Compare commits

..

172 Commits

Author SHA1 Message Date
Stefan Zermatten
0e4918d57d Merge branch 'hotfix-style' 2015-07-27 12:08:22 +02:00
Stefan Zermatten
949f313af2 removed 1st column of all tables being 100px 2015-07-27 12:08:15 +02:00
Stefan Zermatten
2141d52a7a Merge branch 'release-0.6.6' 2015-07-27 11:22:41 +02:00
Stefan Zermatten
4c84235064 Bumped version 2015-07-27 11:22:26 +02:00
Stefan Zermatten
0e194a5408 Added markdown to text areas 2015-07-27 10:21:26 +02:00
Stefan Zermatten
4b60eac330 Fixed typo on change log 2015-07-24 11:52:46 +02:00
Stefan Zermatten
cb017c359d Merge branch 'release-0.6.5' into develop 2015-07-24 11:31:05 +02:00
Stefan Zermatten
15aaaa5c14 Merge branch 'release-0.6.5' 2015-07-24 11:29:50 +02:00
Stefan Zermatten
290bee83b4 Bumped version 2015-07-24 11:29:03 +02:00
Stefan Zermatten
fcd2461205 Merge branch 'master' into release-0.6.5 2015-07-24 11:26:46 +02:00
Stefan Zermatten
52198d0249 Disabled edit buttons for read-only viewers 2015-07-23 15:07:39 +02:00
Stefan Zermatten
ba7ccfdfa0 Added support for character profile pictures 2015-07-23 15:04:43 +02:00
Stefan Zermatten
92d3b086fa net worth calculations now take into account your containers' values 2015-07-23 09:14:55 +02:00
Stefan Zermatten
e180595dcd Merge branch 'release-0.6.4' 2015-07-06 13:37:02 +02:00
Stefan Zermatten
ed708bdde0 bumped version 2015-07-06 13:36:50 +02:00
Stefan Zermatten
d7852d640f Fixed Math using min and max incorrectly in skills 2015-07-03 13:21:26 +02:00
Stefan Zermatten
a2fdee88b6 Ordered Character lists 2015-06-29 14:00:55 +02:00
Stefan Zermatten
af070b1578 Attacks inherit spell names correctly 2015-06-29 13:55:27 +02:00
Stefan Zermatten
83a8eeef0f Fixed editing of multiple attacks 2015-06-29 13:55:15 +02:00
Stefan Zermatten
34f8e7402b Hit dice constitution now has a + sign when positive 2015-06-29 10:21:49 +02:00
Stefan Zermatten
1b7e2cd850 Merge branch 'hotfix-rounding' into develop 2015-06-29 10:04:45 +02:00
Stefan Zermatten
463b7f0fc9 Merge branch 'hotfix-rounding' 2015-06-29 09:45:38 +02:00
Stefan Zermatten
266495abc8 Bumped version 2015-06-29 09:44:57 +02:00
Stefan Zermatten
453d4365d3 Prevent memoized values being used in dependency loops 2015-06-29 09:41:01 +02:00
Stefan Zermatten
e0ce6275bf Floor character attribute values 2015-06-29 09:40:37 +02:00
Stefan Zermatten
16b16ce6c6 Merge branch 'hotfix-minmax' into develop 2015-06-26 10:49:10 +02:00
Stefan Zermatten
80c72a274e Merge branch 'hotfix-minmax' 2015-06-26 10:48:43 +02:00
Stefan Zermatten
91f0f7954c Bumped version 2015-06-26 10:48:33 +02:00
Stefan Zermatten
c74abcb608 Fixed min and max error 2015-06-26 10:48:04 +02:00
Stefan Zermatten
da8b91594e Removed reference to character helper 2015-06-25 14:22:07 +02:00
Stefan Zermatten
fc26f5a73e Merge branch 'hotfix-experience-inputs' into develop 2015-06-25 14:20:54 +02:00
Stefan Zermatten
4f60766d5d Merge branch 'hotfix-experience-edit-inputs' 2015-06-25 14:07:34 +02:00
Stefan Zermatten
e992aeebef Bumped version 2015-06-25 14:07:24 +02:00
Stefan Zermatten
4108346a98 Fixed experience inputs updating 2015-06-25 14:07:13 +02:00
Stefan Zermatten
946fadadc2 Merge branch 'release-0.6.0' 2015-06-25 13:49:25 +02:00
Stefan Zermatten
d2cc2833a9 Merge branch 'release-0.6.0' into develop 2015-06-25 13:48:32 +02:00
Stefan Zermatten
af57326194 Bumped version 2015-06-25 13:48:12 +02:00
Stefan Zermatten
98c69e9e17 Style tweaks 2015-06-25 13:38:33 +02:00
Stefan Zermatten
395edd0563 Character settings now formatted and has done button 2015-06-25 13:38:20 +02:00
Stefan Zermatten
43e87e7786 Container weight summaries now round off 2015-06-25 13:37:55 +02:00
Stefan Zermatten
ad347504c6 Fixed experienceDialog updating polymer inputs 2015-06-25 13:37:03 +02:00
Stefan Zermatten
4e6e99b695 Fixed spellDialog formatting 2015-06-25 13:36:42 +02:00
Stefan Zermatten
104624a322 Merge branch 'feature-soft-memoize' into develop 2015-06-25 13:35:19 +02:00
Stefan Zermatten
79d166e6af Removed references to old calculations 2015-06-25 13:34:44 +02:00
Stefan Zermatten
86c934e8ac Began replacing calls to helpers with calls to memoized functions 2015-06-18 15:14:37 +02:00
Stefan Zermatten
a034cbf30e Duplicate character helpers with memoized functions not attached to characters 2015-06-18 13:34:59 +02:00
Stefan Zermatten
d5680ebf8a Add memoize functionality 2015-06-18 13:33:54 +02:00
Stefan Zermatten
53f2fcc945 Merge branch 'release-0.5.7' into develop 2015-06-17 14:32:35 +02:00
Stefan Zermatten
612e127be4 Merge branch 'release-0.5.7' 2015-06-17 14:22:33 +02:00
Stefan Zermatten
2b0d975cee change logs 2015-06-17 14:22:12 +02:00
Stefan Zermatten
248ab9bb6b Added moving item dialog, that can't actually be triggered 2015-06-17 14:15:49 +02:00
Stefan Zermatten
314ce85410 Attacks can now be added to spells 2015-06-17 09:05:23 +02:00
Stefan Zermatten
9ff45dbcc2 backgrounds now have proficiencies 2015-06-17 08:49:26 +02:00
Stefan Zermatten
6caf19bc99 Merge branch 'release-0.5.6' into develop 2015-06-15 13:36:50 +02:00
Stefan Zermatten
c2b04d0977 Merge branch 'release-0.5.6' 2015-06-15 13:34:29 +02:00
Stefan Zermatten
9012c4a558 change logs 2015-06-15 13:34:10 +02:00
Stefan Zermatten
65a84937f2 Merge branch 'feature-share-publically' into develop 2015-06-15 13:30:42 +02:00
Stefan Zermatten
eebb88b6b1 New front page and darker style 2015-06-15 13:29:33 +02:00
Stefan Zermatten
06ab7c5116 Merge branch 'master' into feature-share-publically 2015-06-15 10:04:40 +02:00
Stefan Zermatten
89f03c7601 Merge branch 'hotfix-gmail-report' 2015-06-12 08:04:23 +02:00
Stefan Zermatten
9d2eb14c0c Change logs 2015-06-12 08:03:46 +02:00
Stefan Zermatten
7b3cb54983 Added gmail email address senders to the report emails 2015-06-12 08:02:33 +02:00
Stefan Zermatten
a09bad2fed Change logs 2015-06-10 11:13:58 +02:00
Stefan Zermatten
afd897edfe Merge branch 'Hotfix' 2015-06-10 11:05:01 +02:00
Stefan Zermatten
efc79cb6e7 Fixed net value calculation to avoid rounding errors 2015-06-10 11:00:42 +02:00
Stefan Zermatten
35efe39ea7 Made feedback reports send emails "from" their creator's address 2015-06-10 11:00:19 +02:00
Stefan Zermatten
034067bd6e Added link to example character 2015-06-10 10:58:47 +02:00
Stefan Zermatten
0d75cd5d15 Added sharing to anyone with link, changed home page 2015-06-09 17:06:51 +02:00
Stefan Zermatten
4f1376a666 Change log 2015-06-04 09:57:49 +02:00
Stefan Zermatten
78b1d71b9d Overhauled how effects are edited 2015-05-27 13:13:51 +02:00
Stefan Zermatten
1323d8006c Made feedback not sendable without title & description 2015-05-27 09:18:34 +02:00
Stefan Zermatten
87d722adaf Now send emails to myself when feedback gets reported 2015-05-27 08:33:24 +02:00
Stefan Zermatten
90e511eb00 Added the ability to hide the spells tab for a character 2015-05-27 08:10:14 +02:00
Stefan Zermatten
5b8c25f5de Fixed a harmless error with un-set effect views 2015-05-27 08:04:39 +02:00
Stefan Zermatten
2fbc54fee8 Edited the guide to be "open beta" 2015-05-25 09:23:38 +02:00
Stefan Zermatten
a064ae3fe8 Added Kadira 2015-05-25 08:33:14 +02:00
Stefan Zermatten
ba9b518d7e Added subsManager 2015-05-22 14:36:05 +02:00
Stefan Zermatten
2f729070b2 Updated change log 2015-05-22 14:24:22 +02:00
Stefan Zermatten
7aedb9451c Base values now don't look like added values 2015-05-22 14:17:38 +02:00
Stefan Zermatten
c6886dd49e Floaty menus now close when clicking on a sub-button 2015-05-22 14:14:20 +02:00
Stefan Zermatten
038ce490e4 Added subsmanager to stop characters getting forgotten between page changes 2015-05-22 14:11:22 +02:00
Stefan Zermatten
52bef57637 Added encumbrance effects, conditions and encumbrance buffs 2015-05-22 14:04:09 +02:00
Stefan Zermatten
29e9f8c8dc Added quality-of-life UI to determining a character's encumbrance 2015-05-20 16:14:01 +02:00
Stefan Zermatten
fea02811ff Added buffs and standard conditions, no UI yet though 2015-05-20 16:13:25 +02:00
Stefan Zermatten
73cee52fff Fixed a bug in combining multiple resistances/vulnerabilities 2015-05-20 16:11:59 +02:00
Stefan Zermatten
b58c006ed4 Updated change log 2015-05-19 11:45:38 +02:00
Stefan Zermatten
ef44f6c1a5 Fixed migration of attack data 2015-05-19 11:44:06 +02:00
Stefan Zermatten
f455cea43f Fixed maths for strength calculations rounding, rather than rounding down 2015-05-18 14:31:07 +02:00
Stefan Zermatten
e4083bc744 Updated the change log 2015-05-18 14:16:24 +02:00
Stefan Zermatten
baffafb62a Added table of calculated values to strength detail 2015-05-18 14:13:28 +02:00
Stefan Zermatten
4143929667 Resolve merge conflicts 2015-05-18 13:21:28 +02:00
Stefan Zermatten
18286d1b9c Merge remote-tracking branch 'origin/master'
Conflicts:
	rpg-docs/client/style/main.scss
2015-05-18 13:20:24 +02:00
Stefan Zermatten
9f51567162 Added migrations for new data structure 2015-05-18 13:18:37 +02:00
Stefan Zermatten
66d8a3bfbf Moved dice.js and renamed it 2015-05-18 13:18:27 +02:00
Stefan Zermatten
a9648c10cc Fixed line break formatting in dialogs 2015-05-18 13:18:11 +02:00
Stefan Zermatten
679292373c Added item increment buttons 2015-05-18 13:17:40 +02:00
Stefan Zermatten
ae416458b5 Made attacks less rigid, now using inline computations 2015-05-18 13:16:33 +02:00
Stefan Zermatten
0ff4a887ea Updated the change log with v0.3.1 2015-05-16 00:26:00 +02:00
Stefan Zermatten
955794b5c0 Re-implemented paper-fab menu 2015-05-16 00:20:49 +02:00
Stefan Zermatten
b0ac1dcc29 Split cards into their own scss 2015-05-16 00:20:31 +02:00
Stefan Zermatten
83150bc527 Updated the change log 2015-05-15 16:59:54 +02:00
Stefan Zermatten
061f1fd0a5 Rewrite all css to scss and refactor html 2015-05-15 16:55:05 +02:00
Stefan Zermatten
e40dd196e6 Migrated stats page to scss 2015-05-13 15:25:22 +02:00
Stefan Zermatten
5dbb59ef80 skill-row now scss 2015-05-13 13:53:53 +02:00
Stefan Zermatten
49e25d7304 Started implementing core styles 2015-05-13 13:23:44 +02:00
Stefan Zermatten
85df0257e2 Added scss 2015-05-13 13:23:18 +02:00
Stefan Zermatten
b88bb95928 Stopped the sidebar from appearing except on displays> 900px 2015-05-13 12:51:35 +02:00
Stefan Zermatten
2122e543d5 Ensured disabled effects don't show up in skill and attribute detail boxes 2015-05-13 08:46:28 +02:00
Stefan Zermatten
a71519aaa7 Updated Polymer 2015-05-13 08:46:04 +02:00
Stefan Zermatten
2404845d51 Styling and rounding fixes for detail boxes 2015-05-12 11:34:56 +02:00
Stefan Zermatten
bf032bcdf3 Now only show edit and add buttons to writers, not readers 2015-05-12 11:34:37 +02:00
Stefan Zermatten
ff8ae89722 Improved feedback form style 2015-05-12 10:36:15 +02:00
Stefan Zermatten
80ca7307ce Added change log 2015-05-12 10:10:15 +02:00
Stefan Zermatten
a539b0bc6c limited how much info gets published to users about themselves 2015-05-12 09:33:32 +02:00
Stefan Zermatten
c6b3cad9c8 Improved character side list style 2015-05-12 09:32:40 +02:00
Stefan Zermatten
95b7b66390 Added quick feedback form 2015-05-12 09:32:28 +02:00
Stefan Zermatten
43c4122fe3 Fixed stack dragging within the same container 2015-05-11 17:10:58 +02:00
Stefan Zermatten
3f4dcc146a fixed charId's being out of date after re-parenting 2015-05-11 17:10:28 +02:00
Stefan Zermatten
e4600decd0 Added more things to except list for need sign in 2015-05-11 16:51:17 +02:00
Stefan Zermatten
f6df716870 Rewrote how item drag and drop works. Need to update charId's to keep up. 2015-05-11 16:51:02 +02:00
Stefan Zermatten
b99da301cd Updated meteor 2015-05-11 12:16:38 +02:00
Stefan Zermatten
0a01885300 Finished implementing useraccounts 2015-05-11 12:15:00 +02:00
Stefan Zermatten
5cb1515235 Began implementing useraccounts and permissions properly 2015-05-08 12:59:38 +02:00
Stefan Zermatten
7430c2c795 Prevented evaluate crashing when given duff abilityMod strings 2015-05-06 12:12:18 +02:00
Stefan Zermatten
39f7548b8d Fixed singlecharacter publication returning undefined rather than an empty array 2015-05-06 12:11:37 +02:00
Stefan Zermatten
c4a8c4b7ba Fixed typos in the guide, floating action buttons are on the bottom RIGHT not left 2015-05-04 10:01:47 +02:00
Stefan Zermatten
263aba596c Changed Guide styling to be more spacious 2015-05-04 09:53:29 +02:00
Stefan Zermatten
f98ed0b659 Fixed old proficiency dialog showing up after it has been depreciated 2015-04-30 07:42:59 +02:00
Stefan Zermatten
6159ce0e88 Characters menu item now shows up when no characters yet exist 2015-04-30 07:42:30 +02:00
Stefan Zermatten
e9509a3a24 Fixed containers not responding to rename or delete 2015-04-30 07:30:37 +02:00
Stefan Zermatten
aa069fd885 Removed single character subscription vulnerability 2015-04-30 07:18:42 +02:00
Stefan Zermatten
99a64667c6 Removed proficiency from effects 2015-04-29 12:31:51 +02:00
Stefan Zermatten
e05fa064d5 Added basic user guide 2015-04-29 12:25:51 +02:00
Stefan Zermatten
b95636a8a3 Added a side list for characters 2015-04-29 10:16:12 +02:00
Stefan Zermatten
bd6c7cd106 paper sliders no longer jump to points, they need to be dragged 2015-04-29 08:57:09 +02:00
Stefan Zermatten
6b1ff343c2 Persona dialogs now correctly update their input fields 2015-04-29 08:47:55 +02:00
Stefan Zermatten
9e7e027fe9 Share dialog style improvement, can delete shares 2015-04-29 08:38:47 +02:00
Stefan Zermatten
79e0f917df Gave share dialog a done button 2015-04-29 08:16:24 +02:00
Stefan Zermatten
4bddf8d5d3 Racial proficiencies now show up when inserted. 2015-04-29 08:14:01 +02:00
Stefan Zermatten
57159d6cfe Identity dialog now opens as expected 2015-04-29 08:13:09 +02:00
Stefan Zermatten
56957e0ef0 No longer showing ID of users shared to, will show username or nothing 2015-04-29 07:53:00 +02:00
Stefan Zermatten
31e7b8d610 In-progress implementing usernames for share dialog 2015-04-24 12:16:12 +02:00
Stefan Zermatten
e0209df270 Features now always enabled by default 2015-04-24 12:15:50 +02:00
Stefan Zermatten
40500517af Fixed hit dice dialog and arrow positions. 2015-04-24 12:15:32 +02:00
Stefan Zermatten
c1222ad51d Race dialog now animates properly 2015-04-24 11:26:54 +02:00
Stefan Zermatten
07890269fc Changed character menu icon from hamburger to dots 2015-04-23 10:00:50 +02:00
Stefan Zermatten
895a099b9b Added migration methods available to admins 2015-04-23 10:00:31 +02:00
Stefan Zermatten
21dfc5d086 Replaced math library include with ecwyne:mathjs 2015-04-22 14:41:15 +02:00
Stefan Zermatten
fada0f5136 Implemented a javascript code style 2015-04-22 12:44:25 +02:00
Stefan Zermatten
dce20375b5 Fixed and finished implementing attribute summary detail views 2015-04-21 15:42:10 +02:00
Thaum
6926693e9d Implemented skill and attribute summary dialogs 2015-04-21 11:30:34 +00:00
Thaum
b1e23eba9a updated test to match with new proficiency objects 2015-04-20 14:16:38 +00:00
Thaum
012aad5ae9 Prettified all remaining detail boxes to be view -> edit 2015-04-20 14:06:35 +00:00
Thaum
6ec9f09b6a Replaced languages and proficiency strings with proper proficiencies 2015-04-20 09:11:45 +00:00
Thaum
b76ac23713 Made proficiencies their own objects, added migration functionality 2015-04-17 13:56:52 +00:00
Thaum
245867dae6 Removed unneeded "type" from schemas, use parent.collection instead. 2015-04-17 11:30:44 +00:00
Thaum
1352bd1178 Made spell slot container a white-top 2015-04-17 10:41:44 +00:00
Thaum
a5ebe4f452 Fixed error setting character name as title on hot code push 2015-04-17 10:41:13 +00:00
Thaum
e94b35564b Document title now set with each route 2015-04-17 10:35:20 +00:00
Thaum
663102718d Clicking attacks now brings up the relevant item 2015-04-17 10:22:46 +00:00
Thaum
7e4c8a21aa Features detail dialog is now view first with option to edit 2015-04-17 10:13:46 +00:00
Thaum
0536a6ab01 Effect view fixed for conditional benefits 2015-04-17 10:00:06 +00:00
Thaum
e7f0bb5e05 Added favicons... lots of favicons 2015-04-17 08:26:55 +00:00
Thaum
1a1ee142c0 Fixed polymer inputs not showing their titles or their full area 2015-04-16 13:21:45 +00:00
Thaum
e6487c9416 Implemented item attack summaries 2015-04-16 11:52:17 +00:00
Thaum
7cd353894c Improved effects view 2015-04-16 09:47:13 +00:00
Thaum
49a98aec96 Fixed color-change and delete regression on item dialogs 2015-04-16 09:07:29 +00:00
Thaum
8ec3b74299 Iterated on proficiencies towards making them first class assets 2015-04-16 09:02:00 +00:00
Thaum
6aeb6635a7 whitespace cleanup 2015-04-16 09:01:25 +00:00
Thaum
638209719e Changed item detail to a detail view with the option to edit 2015-04-16 09:00:48 +00:00
Thaum
690eea4f29 Spell lists now expand horizontally if there is space. 2015-04-15 08:18:16 +00:00
Thaum
ad43a1d331 Loading screen now has hints 2015-04-15 06:53:39 +00:00
268 changed files with 7386 additions and 3642 deletions

56
.jscsrc Normal file
View File

@@ -0,0 +1,56 @@
{
"requireOperatorBeforeLineBreak": true,
"requireCamelCaseOrUpperCaseIdentifiers": true,
"maximumLineLength": {
"value": 80,
"allowComments": true,
"allowRegex": true
},
"validateIndentation": "\t",
"validateQuoteMarks": "\"",
"disallowMultipleLineStrings": true,
"disallowMixedSpacesAndTabs": "smart",
"disallowTrailingWhitespace": true,
"disallowSpaceAfterPrefixUnaryOperators": true,
"disallowMultipleVarDecl": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowKeywordsOnNewLine": ["else"],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpacesInForStatement": true,
"requireTrailingComma": {
"ignoreSingleValue": true,
"ignoreSingleLine": true
},
"requireLineFeedAtFileEnd": true,
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"disallowSpacesInsideParentheses": true,
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true
}

3
.jshintrc Normal file
View File

@@ -0,0 +1,3 @@
{
"undef": false
}

3
rpg-docs/.gitignore vendored
View File

@@ -1,4 +1,5 @@
.meteor/local .meteor/local
.meteor/meteorite .meteor/meteorite
public/components public/components
nohup.out nohup.out
dump

View File

@@ -16,5 +16,15 @@ differential:vulcanize
matb33:collection-hooks matb33:collection-hooks
zimme:collection-softremovable zimme:collection-softremovable
momentjs:moment momentjs:moment
mike:mocha
dburles:mongo-collection-instances dburles:mongo-collection-instances
percolate:migrations
ecwyne:mathjs
useraccounts:polymer
accounts-google
splendido:accounts-meld
email
fourseven:scss@2.1.1
wolves:bourbon
meteorhacks:subs-manager
meteorhacks:kadira
chuangbo:marked

View File

@@ -1,10 +1,11 @@
accounts-base@1.2.0 accounts-base@1.2.0
accounts-google@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1 accounts-password@1.1.1
accounts-ui@1.1.5 accounts-ui@1.1.5
accounts-ui-unstyled@1.1.7 accounts-ui-unstyled@1.1.7
aldeed:collection2@2.3.3 aldeed:collection2@2.3.3
aldeed:simple-schema@1.3.2 aldeed:simple-schema@1.3.3
amplify@1.0.0
autoupdate@1.2.1 autoupdate@1.2.1
base64@1.0.3 base64@1.0.3
binary-heap@1.0.3 binary-heap@1.0.3
@@ -13,16 +14,20 @@ blaze-tools@1.0.3
boilerplate-generator@1.0.3 boilerplate-generator@1.0.3
callback-hook@1.0.3 callback-hook@1.0.3
check@1.0.5 check@1.0.5
chuangbo:marked@0.3.5
coffeescript@1.0.6 coffeescript@1.0.6
dburles:collection-helpers@1.0.3 dburles:collection-helpers@1.0.3
dburles:mongo-collection-instances@0.3.3 dburles:mongo-collection-instances@0.3.3
ddp@1.1.0 ddp@1.1.0
deps@1.0.7 deps@1.0.7
differential:vulcanize@0.0.4 differential:vulcanize@0.0.5
ecwyne:mathjs@0.25.0
ejson@1.0.6 ejson@1.0.6
email@1.0.6 email@1.0.6
fastclick@1.0.3 fastclick@1.0.3
fourseven:scss@2.1.1
geojson-utils@1.0.3 geojson-utils@1.0.3
google@1.1.5
html-tools@1.0.4 html-tools@1.0.4
htmljs@1.0.4 htmljs@1.0.4
http@1.1.0 http@1.1.0
@@ -43,20 +48,24 @@ less@1.0.14
livedata@1.0.13 livedata@1.0.13
localstorage@1.0.3 localstorage@1.0.3
logging@1.0.7 logging@1.0.7
matb33:collection-hooks@0.7.11 matb33:collection-hooks@0.7.13
meteor@1.1.6 meteor@1.1.6
meteor-platform@1.2.2 meteor-platform@1.2.2
mike:mocha@0.5.2 meteorhacks:kadira@2.21.0
meteorhacks:meteorx@1.3.1
meteorhacks:subs-manager@1.3.0
minifiers@1.1.5 minifiers@1.1.5
minimongo@1.0.8 minimongo@1.0.8
mobile-status-bar@1.0.3 mobile-status-bar@1.0.3
momentjs:moment@2.10.0 momentjs:moment@2.10.3
mongo@1.1.0 mongo@1.1.0
mongo-livedata@1.0.8
npm-bcrypt@0.7.8_2 npm-bcrypt@0.7.8_2
oauth@1.1.4
oauth2@1.1.3
observe-sequence@1.0.6 observe-sequence@1.0.6
ordered-dict@1.0.3 ordered-dict@1.0.3
practicalmeteor:chai@1.9.2_3 percolate:migrations@0.7.5
practicalmeteor:loglevel@1.1.0_3
random@1.0.3 random@1.0.3
reactive-dict@1.1.0 reactive-dict@1.1.0
reactive-var@1.0.5 reactive-var@1.0.5
@@ -66,20 +75,21 @@ routepolicy@1.0.5
service-configuration@1.0.4 service-configuration@1.0.4
session@1.1.0 session@1.1.0
sha@1.0.3 sha@1.0.3
softwarerero:accounts-t9n@1.0.9
spacebars@1.0.6 spacebars@1.0.6
spacebars-compiler@1.0.6 spacebars-compiler@1.0.6
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.0
srp@1.0.3 srp@1.0.3
templating@1.1.1 templating@1.1.1
tracker@1.0.7 tracker@1.0.7
ui@1.0.6 ui@1.0.6
underscore@1.0.3 underscore@1.0.3
url@1.0.4 url@1.0.4
velocity:core@0.4.5 useraccounts:core@1.9.1
velocity:html-reporter@0.3.2 useraccounts:polymer@1.9.1
velocity:node-soft-mirror@0.3.1
velocity:shim@0.1.0
velocity:test-proxy@0.0.4
webapp@1.2.0 webapp@1.2.0
webapp-hashing@1.0.3 webapp-hashing@1.0.3
wolves:bourbon@1.0.0
zimme:collection-behaviours@1.0.4 zimme:collection-behaviours@1.0.4
zimme:collection-softremovable@1.0.4 zimme:collection-softremovable@1.0.4

View File

@@ -4,4 +4,4 @@ Schemas.Instance = new SimpleSchema({
//an instance is a single flow of time all parties in an instance are in-sync time wise //an instance is a single flow of time all parties in an instance are in-sync time wise
}); });
Instances.attachSchema(Schemas.Instance); Instances.attachSchema(Schemas.Instance);

View File

@@ -5,4 +5,4 @@ Schemas.Party = new SimpleSchema({
//each party can only be in a single instance at a time //each party can only be in a single instance at a time
}); });
Parties.attachSchema(Schemas.Party); Parties.attachSchema(Schemas.Party);

View File

@@ -5,29 +5,32 @@ Actions = new Mongo.Collection("actions");
*/ */
Schemas.Action = new SimpleSchema({ Schemas.Action = new SimpleSchema({
charId: { charId: {
type: String, type: String,
regEx: SimpleSchema.RegEx.Id regEx: SimpleSchema.RegEx.Id,
}, },
name: { name: {
type: String, trim: false type: String,
trim: false,
}, },
description: { description: {
type: String, trim: false type: String,
trim: false,
}, },
type: { type: {
type: String, type: String,
allowedValues: ["action, bonus, reaction, free"], allowedValues: ["action, bonus, reaction, free"],
defaultValue: "action" defaultValue: "action",
}, },
//the immediate impact of doing this action (eg. -1 rages) //the immediate impact of doing this action (eg. -1 rages)
adjustments: { adjustments: {
type: [Schemas.Adjustment], defaultValue: [] type: [Schemas.Adjustment],
} defaultValue: [],
},
}); });
Actions.attachSchema(Schemas.Action); Actions.attachSchema(Schemas.Action);
Actions.attachBehaviour('softRemovable'); Actions.attachBehaviour("softRemovable");
makeChild(Actions); makeChild(Actions);
Actions.allow(CHARACTER_SUBSCHEMA_ALLOW); Actions.allow(CHARACTER_SUBSCHEMA_ALLOW);

View File

@@ -5,68 +5,69 @@ Attacks = new Mongo.Collection("attacks");
*/ */
Schemas.Attack = new SimpleSchema({ Schemas.Attack = new SimpleSchema({
charId: { charId: {
type: String, type: String,
regEx: SimpleSchema.RegEx.Id regEx: SimpleSchema.RegEx.Id,
}, },
name: { name: {
type: String, type: String,
defaultValue: "New Attack", defaultValue: "New Attack",
trim: false trim: false,
}, },
details: { details: {
type: String, type: String,
optional: true, optional: true,
trim: false trim: false,
}, },
attackBonus: { attackBonus: {
type: String, type: String,
defaultValue: "strengthMod + proficiencyBonus", defaultValue: "strengthMod + proficiencyBonus",
optional: true,
trim: false
},
damageBonus: {
type: String,
defaultValue: "strengthMod",
optional: true,
trim: false
},
damageDice: {
type: String,
optional: true, optional: true,
defaultValue: "1d8", trim: false,
allowedValues: DAMAGE_DICE },
damage: {
type: String,
defaultValue: "1d8 + {strengthMod}",
optional: true,
trim: false,
}, },
damageType: { damageType: {
type: String, type: String,
allowedValues: ["bludgeoning", "piercing", "slashing", "acid", "cold", "fire", "force", "lightning", "necrotic", allowedValues: [
"poison", "psychic", "radiant", "thunder"], "bludgeoning",
defaultValue: "slashing" "piercing",
}, "slashing",
//indicates what the attack originated from "acid",
type: { "cold",
type: String, "fire",
defaultValue: "editable", "force",
allowedValues: ["editable", "feature", "class", "buff", "equipment", "racial", "inate"] "lightning",
"necrotic",
"poison",
"psychic",
"radiant",
"thunder",
],
defaultValue: "slashing",
}, },
//the id of the feature, buff or item that created this effect //the id of the feature, buff or item that created this effect
parent: { parent: {
type: Schemas.Parent type: Schemas.Parent
}, },
color: { color: {
type: String, type: String,
allowedValues: _.pluck(colorOptions, "key"), allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q" defaultValue: "q",
}, },
enabled: { enabled: {
type: Boolean, type: Boolean,
defaultValue: true defaultValue: true,
} },
}); });
Attacks.attachSchema(Schemas.Attack); Attacks.attachSchema(Schemas.Attack);
Attacks.attachBehaviour('softRemovable'); Attacks.attachBehaviour("softRemovable");
makeChild(Attacks, ['name', 'enabled']); //children of lots of things makeChild(Attacks, ["name", "enabled"]); //children of lots of things
Attacks.allow(CHARACTER_SUBSCHEMA_ALLOW); Attacks.allow(CHARACTER_SUBSCHEMA_ALLOW);
Attacks.deny(CHARACTER_SUBSCHEMA_DENY); Attacks.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,27 +1,51 @@
Buffs = new Mongo.Collection("buffs"); Buffs = new Mongo.Collection("buffs");
//buffs are temporary once applied and store things which expire and their expiry time
Schemas.Buff = new SimpleSchema({ Schemas.Buff = new SimpleSchema({
//buff id charId: {
_id: {
type: String, type: String,
regEx: SimpleSchema.RegEx.Id, regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if(!this.isSet) return Random.id();
}},
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id
}, },
//expiry time name: {
expiry: { type: Number, optional: true}, type: String,
duration: { type: Number } trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
enabled: {
type: Boolean,
defaultValue: true,
},
type: {
type: String,
allowedValues: [
"inate",
"custom",
],
},
"lifeTime.total": {
type: Number,
defaultValue: 0, //0 is infinite
min: 0,
},
"lifeTime.spent": {
type: Number,
defaultValue: 0,
min: 0,
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Buffs.attachSchema(Schemas.Buff); Buffs.attachSchema(Schemas.Buff);
Buffs.attachBehaviour('softRemovable'); Buffs.attachBehaviour("softRemovable");
makeParent(Buffs, 'name'); //parents of effects and attacks makeParent(Buffs, ["name", "enabled"]); //parents of effects
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW); Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
Buffs.deny(CHARACTER_SUBSCHEMA_DENY); Buffs.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -3,18 +3,17 @@ Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({ Schemas.Character = new SimpleSchema({
//strings //strings
name: { type: String, defaultValue: "", trim: false}, name: {type: String, defaultValue: "", trim: false, optional: true},
alignment: { type: String, defaultValue: "", trim: false}, alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: { type: String, defaultValue: "", trim: false}, gender: {type: String, defaultValue: "", trim: false, optional: true},
race: { type: String, defaultValue: "", trim: false}, race: {type: String, defaultValue: "", trim: false, optional: true},
description: { type: String, defaultValue: "", trim: false}, picture: {type: String, defaultValue: "", trim: true, optional: true},
personality: { type: String, defaultValue: "", trim: false}, description: {type: String, defaultValue: "", trim: false, optional: true},
ideals: { type: String, defaultValue: "", trim: false}, personality: {type: String, defaultValue: "", trim: false, optional: true},
bonds: { type: String, defaultValue: "", trim: false}, ideals: {type: String, defaultValue: "", trim: false, optional: true},
flaws: { type: String, defaultValue: "", trim: false}, bonds: {type: String, defaultValue: "", trim: false, optional: true},
backstory: { type: String, defaultValue: "", trim: false}, flaws: {type: String, defaultValue: "", trim: false, optional: true},
proficiencies:{ type: String, defaultValue: "", trim: false}, backstory: {type: String, defaultValue: "", trim: false, optional: true},
languages: { type: String, defaultValue: "", trim: false},
//attributes //attributes
//ability scores //ability scores
@@ -75,143 +74,388 @@ Schemas.Character = new SimpleSchema({
slashingMultiplier: {type: Schemas.Attribute}, slashingMultiplier: {type: Schemas.Attribute},
thunderMultiplier: {type: Schemas.Attribute}, thunderMultiplier: {type: Schemas.Attribute},
//skills //skills
//saves //saves
strengthSave: {type: Schemas.Skill}, strengthSave: {type: Schemas.Skill},
"strengthSave.ability": { type: String, defaultValue: "strength" }, "strengthSave.ability": {type: String, defaultValue: "strength"},
dexteritySave: {type: Schemas.Skill}, dexteritySave: {type: Schemas.Skill},
"dexteritySave.ability": { type: String, defaultValue: "dexterity" }, "dexteritySave.ability": {type: String, defaultValue: "dexterity"},
constitutionSave:{type: Schemas.Skill}, constitutionSave:{type: Schemas.Skill},
"constitutionSave.ability": { type: String, defaultValue: "constitution" }, "constitutionSave.ability": {type: String, defaultValue: "constitution"},
intelligenceSave:{type: Schemas.Skill}, intelligenceSave:{type: Schemas.Skill},
"intelligenceSave.ability": { type: String, defaultValue: "intelligence" }, "intelligenceSave.ability": {type: String, defaultValue: "intelligence"},
wisdomSave: {type: Schemas.Skill}, wisdomSave: {type: Schemas.Skill},
"wisdomSave.ability": { type: String, defaultValue: "wisdom" }, "wisdomSave.ability": {type: String, defaultValue: "wisdom"},
charismaSave: {type: Schemas.Skill}, charismaSave: {type: Schemas.Skill},
"charismaSave.ability": { type: String, defaultValue: "charisma" }, "charismaSave.ability": {type: String, defaultValue: "charisma"},
//skill skills //skill skills
acrobatics: {type: Schemas.Skill}, acrobatics: {type: Schemas.Skill},
"acrobatics.ability": { type: String, defaultValue: "dexterity" }, "acrobatics.ability": {type: String, defaultValue: "dexterity"},
animalHandling: {type: Schemas.Skill}, animalHandling: {type: Schemas.Skill},
"animalHandling.ability": { type: String, defaultValue: "wisdom" }, "animalHandling.ability": {type: String, defaultValue: "wisdom"},
arcana: {type: Schemas.Skill}, arcana: {type: Schemas.Skill},
"arcana.ability": { type: String, defaultValue: "intelligence" }, "arcana.ability": {type: String, defaultValue: "intelligence"},
athletics: {type: Schemas.Skill}, athletics: {type: Schemas.Skill},
"athletics.ability": { type: String, defaultValue: "strength" }, "athletics.ability": {type: String, defaultValue: "strength"},
deception: {type: Schemas.Skill}, deception: {type: Schemas.Skill},
"deception.ability": { type: String, defaultValue: "charisma" }, "deception.ability": {type: String, defaultValue: "charisma"},
history: {type: Schemas.Skill}, history: {type: Schemas.Skill},
"history.ability": { type: String, defaultValue: "intelligence" }, "history.ability": {type: String, defaultValue: "intelligence"},
insight: {type: Schemas.Skill}, insight: {type: Schemas.Skill},
"insight.ability": { type: String, defaultValue: "wisdom" }, "insight.ability": {type: String, defaultValue: "wisdom"},
intimidation: {type: Schemas.Skill}, intimidation: {type: Schemas.Skill},
"intimidation.ability": { type: String, defaultValue: "charisma" }, "intimidation.ability": {type: String, defaultValue: "charisma"},
investigation: {type: Schemas.Skill}, investigation: {type: Schemas.Skill},
"investigation.ability": { type: String, defaultValue: "intelligence" }, "investigation.ability": {type: String, defaultValue: "intelligence"},
medicine: {type: Schemas.Skill}, medicine: {type: Schemas.Skill},
"medicine.ability": { type: String, defaultValue: "wisdom" }, "medicine.ability": {type: String, defaultValue: "wisdom"},
nature: {type: Schemas.Skill}, nature: {type: Schemas.Skill},
"nature.ability": { type: String, defaultValue: "intelligence" }, "nature.ability": {type: String, defaultValue: "intelligence"},
perception: {type: Schemas.Skill}, perception: {type: Schemas.Skill},
"perception.ability": { type: String, defaultValue: "wisdom" }, "perception.ability": {type: String, defaultValue: "wisdom"},
performance: {type: Schemas.Skill}, performance: {type: Schemas.Skill},
"performance.ability": { type: String, defaultValue: "charisma" }, "performance.ability": {type: String, defaultValue: "charisma"},
persuasion: {type: Schemas.Skill}, persuasion: {type: Schemas.Skill},
"persuasion.ability": { type: String, defaultValue: "charisma" }, "persuasion.ability": {type: String, defaultValue: "charisma"},
religion: {type: Schemas.Skill}, religion: {type: Schemas.Skill},
"religion.ability": { type: String, defaultValue: "intelligence" }, "religion.ability": {type: String, defaultValue: "intelligence"},
sleightOfHand: {type: Schemas.Skill}, sleightOfHand: {type: Schemas.Skill},
"sleightOfHand.ability": { type: String, defaultValue: "dexterity" }, "sleightOfHand.ability": {type: String, defaultValue: "dexterity"},
stealth: {type: Schemas.Skill}, stealth: {type: Schemas.Skill},
"stealth.ability": { type: String, defaultValue: "dexterity" }, "stealth.ability": {type: String, defaultValue: "dexterity"},
survival: {type: Schemas.Skill}, survival: {type: Schemas.Skill},
"survival.ability": { type: String, defaultValue: "wisdom" }, "survival.ability": {type: String, defaultValue: "wisdom"},
//Mechanical Skills //Mechanical Skills
initiative: {type: Schemas.Skill}, initiative: {type: Schemas.Skill},
"initiative.ability": { type: String, defaultValue: "dexterity" }, "initiative.ability": {type: String, defaultValue: "dexterity"},
dexterityArmor: {type: Schemas.Skill}, dexterityArmor: {type: Schemas.Skill},
"dexterityArmor.ability": { type: String, defaultValue: "dexterity" }, "dexterityArmor.ability": {type: String, defaultValue: "dexterity"},
//mechanics //mechanics
deathSave: { type: Schemas.DeathSave }, deathSave: {type: Schemas.DeathSave},
//permissions //permissions
owner: { type: String, regEx: SimpleSchema.RegEx.Id }, party: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true},
readers: { type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [] }, owner: {type: String, regEx: SimpleSchema.RegEx.Id},
writers: { type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [] }, readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"}, writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
//TODO add per-character settings //TODO add per-character settings
"settings.experiencesInc": {type: Number, defaultValue: 20}, //how many experiences to load at a time in XP table //how many experiences to load at a time in XP table
"settings.experiencesInc": {type: Number, defaultValue: 20},
//slowed down by carrying too much?
"settings.useVariantEncumbrance": {type: Boolean, defaultValue: false},
"settings.useStandardEncumbrance": {type: Boolean, defaultValue: true},
//hide spellcasting
"settings.hideSpellcasting": {type: Boolean, defaultValue: false},
//show to anyone with link
"settings.viewPermission": {
type: String,
defaultValue: "whitelist",
allowedValues: ["whitelist", "public"],
},
}); });
Characters.attachSchema(Schemas.Character); Characters.attachSchema(Schemas.Character);
var attributeBase = function(charId, statName){ var attributeBase = preventLoop(function(charId, statName){
check(statName, String); check(statName, String);
var effects = Effects.find({charId: charId, stat: statName, enabled: true}).fetch(); //if it's a damage multiplier, we treat it specially
effects = _.groupBy(effects, "operation"); if (_.contains(DAMAGE_MULTIPLIERS, statName)){
var value = _.contains(DAMAGE_MULTIPLIERS, statName)? 1 : 0; var invulnerabilityCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0,
}).count();
if (invulnerabilityCount) return 0;
var resistCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 0.5,
}).count();
var vulnCount = Effects.find({
charId: charId,
stat: statName,
enabled: true,
operation: "mul",
value: 2,
}).count();
if (!resistCount && !vulnCount){
return 1;
} else if (resistCount && !vulnCount){
return 0.5;
} else if (!resistCount && vulnCount){
return 2;
} else {
return 1;
}
}
var value;
var base = 0;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
//start with the highest base value Effects.find({
_.each(effects.base, function(effect){ charId: charId,
var efv = evaluateEffect(charId, effect) stat: statName,
if (efv > value){ enabled: true,
value = efv; operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "base"){
if (value > base) base = value;
} else if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
} }
}); });
//add all the add values var result = (base + add) * mul;
_.each(effects.add, function(effect){ if (result < min) result = min;
value += evaluateEffect(charId, effect); if (result > max) result = max;
});
//multiply all the mul values return Math.floor(result);
_.each(effects.mul, function(effect){ });
value *= evaluateEffect(charId, effect);
});
//ensure value is >= all mins if (Meteor.isClient) {
_.each(effects.min, function(effect){ Template.registerHelper("characterCalculate", function(func, charId, input) {
var min = evaluateEffect(charId, effect); try {
value = value > min? value : min; return Characters.calculate[func](charId, input);
} catch (e){
if (!Characters.calculate[func]){
throw new Error(func + "is not a function name");
} else {
throw e;
}
}
}); });
//ensure value is <= all maxes
_.each(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
value = value < max? value : max;
});
return value;
} }
//create a local memoize with a argument concatenating hash function
var memoize = function(f) {
return Tracker.memoize(f, function() {
return _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
});
};
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(charId, {fields: fieldSelector});
var field = char[fieldName];
if (field === undefined){
throw new Meteor.Error(
"getField failed",
"getField could not find field " +
fieldName +
" in character " +
char._id
);
}
return field;
},
fieldValue: function(charId, fieldName) {
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
"Character's schema does not contain a field called: " + fieldName
);
}
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return Characters.calculate.skillMod(charId, fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return Characters.calculate.attributeValue(charId, fieldName);
}
//fall back to just returning the field itself
return Characters.calculate.getField(charId, fieldName);
},
attributeValue: memoize(function(charId, attributeName){
var attribute = Characters.calculate.getField(charId, attributeName);
//base value
var value = Characters.calculate.attributeBase(charId, attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
}),
attributeBase: memoize(function(charId, attributeName){
return attributeBase(charId, attributeName);
}),
skillMod: memoize(preventLoop(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
//get the final value of the ability score
var ability = Characters.calculate.attributeValue(charId, skill.ability);
//base modifier
var mod = +getMod(ability);
//multiply proficiency bonus by largest value in proficiency array
var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier
mod += prof * Characters.calculate.attributeValue(charId, "proficiencyBonus");
//apply all effects
var value;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
Effects.find({
charId: charId,
stat: skillName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
}
});
var result = (mod + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
return Math.floor(result);
})),
proficiency: memoize(function(charId, skillName){
//return largest value in proficiency array
var prof = Proficiencies.findOne(
{charId: charId, name: skillName, enabled: true},
{sort: {value: -1}}
);
return prof && prof.value || 0;
}),
passiveSkill: memoize(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
var mod = +Characters.calculate.skillMod(charId, skillName);
var value = 10 + mod;
Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
var advantage = Characters.calculate.advantage(charId, skillName);
value += 5 * advantage;
return Math.floor(value);
}),
advantage: memoize(function(charId, skillName){
var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"}
).count();
var disadvantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "disadvantage"}
).count();
if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1;
return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
},
passiveAbility: function(charId, attribute){
var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return 10 + mod;
},
xpLevel: function(charId){
var xp = Characters.calculate.experience(charId);
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
return i;
}
}
if (xp > 355000) return 20;
return 0;
},
level: memoize(function(charId){
var level = 0;
Classes.find({charId: charId}).forEach(function(cls){
level += cls.level;
});
return level;
}),
experience: memoize(function(charId){
var xp = 0;
Experiences.find(
{charId: charId},
{fields: {value: 1}}
).forEach(function(e){
xp += e.value;
});
return xp;
}),
};
var depreciated = function() {
//var err = new Error("this function has been depreciated");
var name = "";
if (Template.instance()){
name = Template.instance().view.name;
}
var logString = "this function has been depreciated \n";
if (name){
logString += "View: " + name + "\n\n";
}
//logString += err.stack + "\n\n---------------------\n\n";
console.log(logString);
};
//functions and calculated values. //functions and calculated values.
//These functions can only rely on this._id since no other //These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters //field is likely to be attached to all returned characters
@@ -219,166 +463,63 @@ Characters.helpers({
//returns the value stored in the field requested //returns the value stored in the field requested
//will set up dependencies on just that field //will set up dependencies on just that field
getField : function(fieldName){ getField : function(fieldName){
var fieldSelector = {}; depreciated();
fieldSelector[fieldName] = 1; return Characters.calculate.getField(this._id, fieldName);
var char = Characters.findOne(this._id, {fields: fieldSelector});
var field = char[fieldName];
if(field === undefined){
throw new Meteor.Error("getField failed",
"getField could not find field " + fieldName + " in character "+ char._id);
}
return field;
}, },
//returns the value of a field //returns the value of a field
fieldValue : function(fieldName){ fieldValue : function(fieldName){
if(!Schemas.Character.schema(fieldName)){ depreciated();
throw new Meteor.Error("Field not found", "Character's schema does not contain a field called: " + fieldName); return Characters.calculate.fieldValue(this._id, fieldName);
}
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return this.skillMod(fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return this.attributeValue(fieldName);
}
//fall back to just returning the field itself
return this.getField(fieldName);
}, },
attributeValue: function(attributeName){ attributeValue: function(attributeName){
var charId = this._id; depreciated();
var attribute = this.getField(attributeName); return Characters.calculate.attributeValue(this._id, attributeName);
//base value },
var value = this.attributeBase(attributeName); attributeBase: function(attributeName){
//plus adjustment depreciated();
value += attribute.adjustment; return Characters.calculate.attributeBase(this._id, attributeName);
return value; },
skillMod: function(skillName){
depreciated();
return Characters.calculate.skillMod(this._id, skillName);
}, },
attributeBase: preventLoop(function(attributeName){
var charId = this._id;
//base value
return attributeBase(charId, attributeName);
}),
skillMod: preventLoop(function(skillName){
var charId = this._id;
var skill = this.getField(skillName);
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
//base modifier
var mod = +getMod(ability)
//multiply proficiency bonus by largest value in proficiency array
var prof = this.proficiency(skillName);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
//apply all effects
var rawEffects = Effects.find({charId: charId, stat: skillName, enabled: true}).fetch();
var effects = _.groupBy(rawEffects, "operation");
_.forEach(effects.add, function(effect){
mod += evaluateEffect(charId, effect);
});
_.forEach(effects.mul, function(effect){
mod *= evaluateEffect(charId, effect);
});
_.forEach(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
mod = mod > min? mod : min;
});
_.forEach(effects.max, function(effect){
var max = evaluateEffect(charId, effect);
mod = mod < max? mod : max;
});
return signedString(mod);
}),
proficiency: function(skillName){ proficiency: function(skillName){
var charId = this._id; depreciated();
//return largest value in proficiency array return Characters.calculate.proficiency(this._id, skillName);
var prof = 0;
Effects.find({charId: charId, stat: skillName, enabled: true}).forEach(function(effect){
if(effect.operation === "proficiency"){
var newProf = evaluateEffect(charId, effect);
if (newProf > prof){
prof = newProf;
}
}
});
return prof;
}, },
passiveSkill: function(skillName){ passiveSkill: function(skillName){
if (_.isString(skillName)){ depreciated();
var skill = this.getField(skillName); return Characters.calculate.passiveSkill(this._id, skillName);
}
var charId = this._id
var mod = +this.skillMod(skillName);
var value = 10 + mod;
Effects.find({charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
return value;
//TODO decide whether (dis)advantage gives (-)+5 to passive checks
}, },
advantage: function(skillName){ advantage: function(skillName){
var charId = this._id depreciated();
var advantage = Effects.find({charId: charId, stat: skillName, enabled: true, operation: "advantage"}).count(); return Characters.calculate.advantage(this._id, skillName);
var disadvantage = Effects.find({charId: charId, stat: skillName, enabled: true, operation: "disadvantage"}).count();
if(advantage && !disadvantage) return 1;
if(disadvantage && !advantage) return -1;
return 0;
}, },
abilityMod: function(attribute){ abilityMod: function(attribute){
return signedString(getMod(this.attributeValue(attribute))); depreciated();
return Characters.calculate.abilityMod(this._id, attribute);
}, },
passiveAbility: function(attribute){ passiveAbility: function(attribute){
var mod = +getMod(this.attributeValue(attribute)); depreciated();
return 10 + mod; return Characters.calculate.passiveAbility(this._id, attribute);
}, },
xpLevel: function(){ xpLevel: function(){
var xp = this.experience(); depreciated();
var xpTable = [0, 300, 900, 2700, 6500, 14000, 23000, 34000, 48000, 64000, return Characters.calculate.xpLevel(this._id);
85000, 100000, 120000, 140000, 165000, 195000, 225000, 265000,
305000, 355000];
for(var i = 0; i < 19; i++){
if(xp < xpTable[i]){
return i;
}
};
if(xp > 355000) return 20;
return 0;
}, },
level: function(){ level: function(){
var level = 0; depreciated();
Classes.find({charId: this._id}).forEach(function(cls){ return Characters.calculate.level(this._id);
level += cls.level;
})
return level;
}, },
experience: function(){ experience: function(){
var xp = 0; depreciated();
Experiences.find({charId: this._id}, {fields: {value: 1}}).forEach(function(e){ return Characters.calculate.experience(this._id);
xp += e.value; },
})
return xp;
}
}); });
//clean up all data related to that character before removing it //clean up all data related to that character before removing it
Characters.after.remove(function (userId, character) { if (Meteor.isServer){
if(Meteor.isServer){ Characters.after.remove(function(userId, character) {
Actions .remove({charId: character._id}); Actions .remove({charId: character._id});
Attacks .remove({charId: character._id}); Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id}); Buffs .remove({charId: character._id});
@@ -391,29 +532,29 @@ Characters.after.remove(function (userId, character) {
SpellLists .remove({charId: character._id}); SpellLists .remove({charId: character._id});
Items .remove({charId: character._id}); Items .remove({charId: character._id});
Containers .remove({charId: character._id}); Containers .remove({charId: character._id});
} });
}); }
Characters.allow({ Characters.allow({
insert: function (userId, doc) { insert: function(userId, doc) {
// the user must be logged in, and the document must be owned by the user // the user must be logged in, and the document must be owned by the user
return (userId && doc.owner === userId); return (userId && doc.owner === userId);
}, },
update: function (userId, doc, fields, modifier) { update: function(userId, doc, fields, modifier) {
// can only change documents you have write access to // can only change documents you have write access to
return doc.owner === userId || return doc.owner === userId ||
_.contains(doc.writers, userId); _.contains(doc.writers, userId);
}, },
remove: function (userId, doc) { remove: function(userId, doc) {
// can only remove your own documents // can only remove your own documents
return doc.owner === userId; return doc.owner === userId;
}, },
fetch: ["owner", "writers"] fetch: ["owner", "writers"],
}); });
Characters.deny({ Characters.deny({
update: function (userId, docs, fields, modifier) { update: function(userId, docs, fields, modifier) {
// can't change owners // can't change owners
return _.contains(fields, 'owner'); return _.contains(fields, "owner");
} }
}); });

View File

@@ -8,21 +8,25 @@ Schemas.Class = new SimpleSchema({
type: Date, type: Date,
autoValue: function() { autoValue: function() {
if (this.isInsert) { if (this.isInsert) {
return new Date; return new Date();
} else if (this.isUpsert) { } else if (this.isUpsert) {
return {$setOnInsert: new Date}; return {$setOnInsert: new Date()};
} else { } else {
this.unset(); this.unset();
} }
} },
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
}, },
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"}
}); });
Classes.attachSchema(Schemas.Class); Classes.attachSchema(Schemas.Class);
Classes.attachBehaviour('softRemovable'); Classes.attachBehaviour("softRemovable");
makeParent(Classes, 'name'); //parents of effects and attacks makeParent(Classes, "name"); //parents of effects and attacks
Classes.allow(CHARACTER_SUBSCHEMA_ALLOW); Classes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Classes.deny(CHARACTER_SUBSCHEMA_DENY); Classes.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -7,33 +7,39 @@ Effects = new Mongo.Collection("effects");
Schemas.Effect = new SimpleSchema({ Schemas.Effect = new SimpleSchema({
charId: { charId: {
type: String, type: String,
regEx: SimpleSchema.RegEx.Id regEx: SimpleSchema.RegEx.Id,
}, },
name: { name: {
type: String, type: String,
optional: true, //TODO make necessary if there is no owner optional: true, //TODO make necessary if there is no owner
trim: false trim: false,
}, },
operation: { operation: {
type: String, type: String,
defaultValue: "add", defaultValue: "add",
allowedValues: ["base", "proficiency","add","mul","min","max","advantage","disadvantage","passiveAdd","fail","conditional"] allowedValues: [
"base",
"proficiency",
"add",
"mul",
"min",
"max",
"advantage",
"disadvantage",
"passiveAdd",
"fail",
"conditional",
],
}, },
value: { value: {
type: Number, type: Number,
decimal: true, decimal: true,
optional: true optional: true,
}, },
calculation: { calculation: {
type: String, type: String,
optional: true, optional: true,
trim: false trim: false,
},
//indicates what the effect originated from
type: {
type: String,
defaultValue: "editable",
allowedValues: ["editable", "feature", "class", "buff", "equipment", "racial", "inate"]
}, },
//the thing that created this effect //the thing that created this effect
parent: { parent: {
@@ -42,69 +48,69 @@ Schemas.Effect = new SimpleSchema({
//which stat the effect is applied to //which stat the effect is applied to
stat: { stat: {
type: String, type: String,
optional: true optional: true,
}, },
enabled: { enabled: {
type: Boolean, type: Boolean,
defaultValue: true defaultValue: true,
} },
}); });
Effects.attachSchema(Schemas.Effect); Effects.attachSchema(Schemas.Effect);
if(Meteor.isServer) Characters.after.insert(function (userId, char) { if (Meteor.isServer) Characters.after.insert(function(userId, char) {
Effects.insert({ Effects.insert({
charId: char._id, charId: char._id,
type: "inate",
name: "Constitution modifier for each level", name: "Constitution modifier for each level",
stat: "hitPoints", stat: "hitPoints",
operation: "add", operation: "add",
calculation: "level * constitutionMod", calculation: "level * constitutionMod",
parent: { parent: {
id: char._id, id: char._id,
collection: "Characters" collection: "Characters",
} group: "Inate",
},
}); });
Effects.insert({ Effects.insert({
charId: char._id, charId: char._id,
type: "inate",
name: "Proficiency bonus by level", name: "Proficiency bonus by level",
stat: "proficiencyBonus", stat: "proficiencyBonus",
operation: "add", operation: "add",
calculation: "floor(level / 4 + 1.75)", calculation: "floor(level / 4 + 1.75)",
parent: { parent: {
id: char._id, id: char._id,
collection: "Characters" collection: "Characters",
} group: "Inate",
},
}); });
Effects.insert({ Effects.insert({
charId: char._id, charId: char._id,
type: "inate",
name: "Dexterity Armor Bonus", name: "Dexterity Armor Bonus",
stat: "armor", stat: "armor",
operation: "add", operation: "add",
calculation: "dexterityArmor", calculation: "dexterityArmor",
parent: { parent: {
id: char._id, id: char._id,
collection: "Characters" collection: "Characters",
} group: "Inate",
},
}); });
Effects.insert({ Effects.insert({
charId: char._id, charId: char._id,
type: "inate",
name: "Natural Armor", name: "Natural Armor",
stat: "armor", stat: "armor",
operation: "base", operation: "base",
value: 10, value: 10,
parent: { parent: {
id: char._id, id: char._id,
collection: "Characters" collection: "Characters",
} group: "Inate",
},
}); });
}); });
Effects.attachBehaviour('softRemovable'); Effects.attachBehaviour("softRemovable");
makeChild(Effects, ['name', 'enabled']); //children of lots of things makeChild(Effects, ["enabled"]); //children of lots of things
Effects.allow(CHARACTER_SUBSCHEMA_ALLOW); Effects.allow(CHARACTER_SUBSCHEMA_ALLOW);
Effects.deny(CHARACTER_SUBSCHEMA_DENY); Effects.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -9,19 +9,19 @@ Schemas.Experience = new SimpleSchema({
type: Date, type: Date,
autoValue: function() { autoValue: function() {
if (this.isInsert) { if (this.isInsert) {
return new Date; return new Date();
} else if (this.isUpsert) { } else if (this.isUpsert) {
return {$setOnInsert: new Date}; return {$setOnInsert: new Date()};
} else { } else {
this.unset(); this.unset();
} }
} },
}, },
}); });
Experiences.attachSchema(Schemas.Experience); Experiences.attachSchema(Schemas.Experience);
Experiences.attachBehaviour('softRemovable'); Experiences.attachBehaviour("softRemovable");
Experiences.allow(CHARACTER_SUBSCHEMA_ALLOW); Experiences.allow(CHARACTER_SUBSCHEMA_ALLOW);
Experiences.deny(CHARACTER_SUBSCHEMA_DENY); Experiences.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -6,10 +6,17 @@ Schemas.Feature = new SimpleSchema({
description: {type: String, optional: true, trim: false}, description: {type: String, optional: true, trim: false},
uses: {type: String, optional: true, trim: false}, uses: {type: String, optional: true, trim: false},
used: {type: Number, defaultValue: 0}, used: {type: Number, defaultValue: 0},
reset: {type: String, allowedValues: ["manual", "longRest", "shortRest"], defaultValue: "manual"}, reset: {
type: String,
allowedValues: ["manual", "longRest", "shortRest"],
defaultValue: "manual",
},
enabled: {type: Boolean, defaultValue: true}, enabled: {type: Boolean, defaultValue: true},
alwaysEnabled:{type: Boolean, defaultValue: true}, alwaysEnabled:{type: Boolean, defaultValue: true},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"} color: {type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Features.attachSchema(Schemas.Feature); Features.attachSchema(Schemas.Feature);
@@ -20,11 +27,11 @@ Features.helpers({
}, },
usesValue: function(){ usesValue: function(){
return evaluate(this.charId, this.uses); return evaluate(this.charId, this.uses);
} },
}); });
Features.attachBehaviour('softRemovable'); Features.attachBehaviour("softRemovable");
makeParent(Features, ['name', 'enabled']); //parents of effects and attacks makeParent(Features, ["name", "enabled"]); //parents of effects and attacks
Features.allow(CHARACTER_SUBSCHEMA_ALLOW); Features.allow(CHARACTER_SUBSCHEMA_ALLOW);
Features.deny(CHARACTER_SUBSCHEMA_DENY); Features.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -4,12 +4,16 @@ Schemas.Note = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id}, charId: {type: String, regEx: SimpleSchema.RegEx.Id},
name: {type: String, trim: false}, name: {type: String, trim: false},
description: {type: String, optional: true, trim: false}, description: {type: String, optional: true, trim: false},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"} color: {
type: String,
allowedValues:_.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Notes.attachSchema(Schemas.Note); Notes.attachSchema(Schemas.Note);
Notes.attachBehaviour('softRemovable'); Notes.attachBehaviour("softRemovable");
Notes.allow(CHARACTER_SUBSCHEMA_ALLOW); Notes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Notes.deny(CHARACTER_SUBSCHEMA_DENY); Notes.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -2,25 +2,35 @@ Proficiencies = new Mongo.Collection("proficiencies");
Schemas.Proficiency = new SimpleSchema({ Schemas.Proficiency = new SimpleSchema({
charId: { charId: {
type: String,
regEx: SimpleSchema.RegEx.Id
},
name: {
type: String, type: String,
trim: false regEx: SimpleSchema.RegEx.Id,
},
name: {
type: String,
trim: false,
optional: true,
},
value: {
type: Number,
allowedValues: [0, 0.5, 1, 2],
defaultValue: 1,
decimal: true,
}, },
//indicates what type of thing proficiency originated from
type: { type: {
type: String, type: String,
defaultValue: "editable", allowedValues: ["skill", "save", "weapon", "armor", "tool", "language"],
allowedValues: ["editable", "feature", "buff", "equipment", "inate"] defaultValue: "skill",
},
enabled: {
type: Boolean,
defaultValue: true,
}, },
}); });
Proficiencies.attachSchema(Schemas.Proficiency); Proficiencies.attachSchema(Schemas.Proficiency);
Proficiencies.attachBehaviour('softRemovable'); Proficiencies.attachBehaviour("softRemovable");
makeChild(Proficiencies); makeChild(Proficiencies);
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW); Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY); Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -7,7 +7,11 @@ Schemas.SpellLists = new SimpleSchema({
saveDC: {type: String, optional: true, trim: false}, saveDC: {type: String, optional: true, trim: false},
attackBonus: {type: String, optional: true, trim: false}, attackBonus: {type: String, optional: true, trim: false},
maxPrepared: {type: String, optional: true, trim: false}, maxPrepared: {type: String, optional: true, trim: false},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"}, color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
"settings.showUnprepared": {type: Boolean, defaultValue: true}, "settings.showUnprepared": {type: Boolean, defaultValue: true},
}); });
@@ -16,14 +20,17 @@ SpellLists.attachSchema(Schemas.SpellLists);
SpellLists.helpers({ SpellLists.helpers({
numPrepared: function(){ numPrepared: function(){
var num = 0; var num = 0;
Spells.find({charId: this.charId, listId: this._id, prepared: 1}, {fields: {prepareCost: 1}}).forEach(function(spell){ Spells.find(
{charId: this.charId, listId: this._id, prepared: 1},
{fields: {prepareCost: 1}}
).forEach(function(spell){
num += spell.prepareCost; num += spell.prepareCost;
}); });
return num; return num;
} }
}); });
SpellLists.attachBehaviour('softRemovable'); SpellLists.attachBehaviour("softRemovable");
makeParent(SpellLists); //parents of spells makeParent(SpellLists); //parents of spells
SpellLists.allow(CHARACTER_SUBSCHEMA_ALLOW); SpellLists.allow(CHARACTER_SUBSCHEMA_ALLOW);

View File

@@ -2,26 +2,67 @@ Spells = new Mongo.Collection("spells");
Schemas.Spell = new SimpleSchema({ Schemas.Spell = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id}, charId: {type: String, regEx: SimpleSchema.RegEx.Id},
prepared: {type: String, defaultValue: "prepared", allowedValues: ["prepared","unprepared","always"]}, prepared: {
name: {type: String, trim: false, defaultValue: "New Spell"}, type: String,
description: {type: String, optional: true, trim: false}, defaultValue: "prepared",
castingTime: {type: String, optional: true, defaultValue: "action", trim: false}, allowedValues: ["prepared", "unprepared", "always"],
range: {type: String, optional: true, trim: false}, },
duration: {type: String, optional: true, trim: false, defaultValue: "Instantaneous"}, name: {
"components.verbal": {type: Boolean, defaultValue: false}, type: String,
"components.somatic": {type: Boolean, defaultValue: false}, trim: false,
"components.material": {type: String, optional: true}, 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.concentration": {type: Boolean, defaultValue: false},
ritual: {type: Boolean, defaultValue: false}, "components.material": {type: String, optional: true},
level: {type: Number, defaultValue: 1}, ritual: {
school: {type: String, defaultValue: "Abjuration", allowedValues: magicSchools}, type: Boolean,
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"} defaultValue: false,
},
level: {
type: Number,
defaultValue: 1,
},
school: {
type: String,
defaultValue: "Abjuration",
allowedValues: magicSchools,
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Spells.attachSchema(Schemas.Spell); Spells.attachSchema(Schemas.Spell);
Spells.attachBehaviour('softRemovable'); Spells.attachBehaviour("softRemovable");
makeChild(Spells); //children of spell lists makeChild(Spells); //children of spell lists
makeParent(Spells, ["name", "enabled"]); //parents of attacks
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW); Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
Spells.deny(CHARACTER_SUBSCHEMA_DENY); Spells.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -3,23 +3,19 @@
* Damage, healing and resource cost/recovery are all adjustments * Damage, healing and resource cost/recovery are all adjustments
*/ */
Schemas.Adjustment = new SimpleSchema({ Schemas.Adjustment = new SimpleSchema({
name: {
type: String,
optional: true
},
//which stat the adjustment is applied to //which stat the adjustment is applied to
stat: { stat: {
type: String, type: String,
optional: true optional: true,
}, },
//the value added to the stat //the value added to the stat
value: { value: {
type: Number, type: Number,
decimal: true, decimal: true,
optional: true optional: true,
}, },
calculation: { calculation: {
type: String, type: String,
optional: true optional: true,
} },
}); });

View File

@@ -3,11 +3,11 @@ Schemas.Attribute = new SimpleSchema({
//should be zero after reset //should be zero after reset
adjustment: { adjustment: {
type: Number, type: Number,
defaultValue: 0 defaultValue: 0,
}, },
reset: { reset: {
type: String, type: String,
defaultValue: "longRest", defaultValue: "longRest",
allowedValues: ["longRest", "shortRest"] allowedValues: ["longRest", "shortRest"],
} },
}); });

View File

@@ -3,20 +3,20 @@ Schemas.DeathSave = new SimpleSchema({
type: Number, type: Number,
min: 0, min: 0,
max: 3, max: 3,
defaultValue: 0 defaultValue: 0,
}, },
fail: { fail: {
type: Number, type: Number,
min: 0, min: 0,
max: 3, max: 3,
defaultValue: 0 defaultValue: 0,
}, },
canDeathSave: { canDeathSave: {
type: Boolean, type: Boolean,
defaultValue: true defaultValue: true,
}, },
stable: { stable: {
type: Boolean, type: Boolean,
defaultValue: false defaultValue: false,
} },
}); });

View File

@@ -1,4 +1,4 @@
Schemas.Skill = new SimpleSchema({ Schemas.Skill = new SimpleSchema({
//attribute name that this skill used as base mod for roll //attribute name that this skill used as base mod for roll
ability: { type: String, defaultValue: "" }, ability: {type: String, defaultValue: ""},
}); });

View File

@@ -5,7 +5,7 @@ Schemas.TemporaryHitPoints = new SimpleSchema({
name: {type: String, optional: true}, name: {type: String, optional: true},
maximum: {type: Number, defaultValue: 0}, maximum: {type: Number, defaultValue: 0},
used: {type: Number, defaultValue: 0}, used: {type: Number, defaultValue: 0},
deleteOnZero:{type: Boolean, defaultValue: true}, deleteOnZero:{type: Boolean, defaultValue: false},
dateAdded: { dateAdded: {
type: Date, type: Date,
autoValue: function() { autoValue: function() {
@@ -16,7 +16,7 @@ Schemas.TemporaryHitPoints = new SimpleSchema({
} else { } else {
this.unset(); this.unset();
} }
} },
}, },
}); });
@@ -29,11 +29,13 @@ TemporaryHitPoints.helpers({
}); });
//remove the temporary hit points when they hit zero //remove the temporary hit points when they hit zero
TemporaryHitPoints.after.update(function (userId, thp, fieldNames, modifier, options) { TemporaryHitPoints.after.update(
if(thp.used >= thp.maximum && thp.deleteOnZero){ function(userId, thp, fieldNames, modifier, options){
TemporaryHitPoints.remove(thp._id); if (thp.used >= thp.maximum && thp.deleteOnZero){
} TemporaryHitPoints.remove(thp._id);
}, {fetchPrevious: false}); }
}, {fetchPrevious: false}
);
TemporaryHitPoints.allow(CHARACTER_SUBSCHEMA_ALLOW); TemporaryHitPoints.allow(CHARACTER_SUBSCHEMA_ALLOW);
TemporaryHitPoints.deny(CHARACTER_SUBSCHEMA_DENY); TemporaryHitPoints.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -2,39 +2,55 @@
Containers = new Mongo.Collection("containers"); Containers = new Mongo.Collection("containers");
Schemas.Container = new SimpleSchema({ Schemas.Container = new SimpleSchema({
name: { type: String, trim: false }, name: {type: String, trim: false},
charId: { type: String, regEx: SimpleSchema.RegEx.Id}, charId: {type: String, regEx: SimpleSchema.RegEx.Id},
isCarried: { type: Boolean }, isCarried: {type: Boolean},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true}, weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true}, value: {type: Number, min: 0, defaultValue: 0, decimal: true},
description:{type: String, optional: true, trim: false}, description:{type: String, optional: true, trim: false},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"} color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Containers.attachSchema(Schemas.Container); Containers.attachSchema(Schemas.Container);
Containers.helpers({ Containers.helpers({
totalValue: function(){ contentsValue: function(){
var value = this.value; var value = 0;
Items.find({"parent.id": this._id}, {fields: {quantity: 1, value: 1}}).forEach(function(item){ Items.find(
{"parent.id": this._id},
{fields: {quantity: 1, value: 1}}
).forEach(function(item){
value += item.totalValue(); value += item.totalValue();
}); });
return value; return value;
}, },
totalWeight: function(){ totalValue: function(){
var weight = this.weight; return this.contentsValue() + this.value;
Items.find({"parent.id": this._id}, {fields: {quantity: 1, weight: 1}}).forEach(function(item){ },
contentsWeight: function(){
var weight = 0;
Items.find(
{"parent.id": this._id},
{fields: {quantity: 1, weight: 1}}
).forEach(function(item){
weight += item.totalWeight(); weight += item.totalWeight();
}); });
return weight; return weight;
}, },
totalWeight: function(){
return this.contentsWeight() + this.weight;
},
moveToCharacter: function(characterId){ moveToCharacter: function(characterId){
if(this.charId === characterId) return; if (this.charId === characterId) return;
Items.update(this._id, {$set: {charId: characterId}}); Items.update(this._id, {$set: {charId: characterId}});
} },
}); });
Containers.attachBehaviour('softRemovable'); Containers.attachBehaviour("softRemovable");
makeParent(Containers); //parents of items makeParent(Containers); //parents of items
Containers.allow(CHARACTER_SUBSCHEMA_ALLOW); Containers.allow(CHARACTER_SUBSCHEMA_ALLOW);

View File

@@ -1,4 +1,4 @@
Items = new Mongo.Collection('items'); Items = new Mongo.Collection("items");
Schemas.Item = new SimpleSchema({ Schemas.Item = new SimpleSchema({
name: {type: String, defaultValue: "New Item", trim: false}, name: {type: String, defaultValue: "New Item", trim: false},
@@ -10,11 +10,166 @@ Schemas.Item = new SimpleSchema({
value: {type: Number, min: 0, defaultValue: 0, decimal: true}, value: {type: Number, min: 0, defaultValue: 0, decimal: true},
enabled: {type: Boolean, defaultValue: false}, enabled: {type: Boolean, defaultValue: false},
requiresAttunement: {type: Boolean, defaultValue: false}, requiresAttunement: {type: Boolean, defaultValue: false},
color: {type: String, allowedValues: _.pluck(colorOptions, "key"), defaultValue: "q"} "settings.showIncrement": {type: Boolean, defaultValue: false},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
}); });
Items.attachSchema(Schemas.Item); Items.attachSchema(Schemas.Item);
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");
//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");
}
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");
}
} else {
var parentCollectionObject = global[parent.collection];
var parentObject = null;
if (parentCollectionObject)
parentObject = parentCollectionObject.findOne(
parent.id, {fields: {_id: 1, charId: 1}}
);
if (!parentObject) throw new Meteor.Error(
"Invalid parent",
"The destination parent " + parent.id +
" does not exist in the collection " + parent.collection
);
if (parentObject.charId){
permission = Meteor.call("canWriteCharacter", parentObject.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move items to this character");
}
}
}
};
var moveItem = function(itemId, enable, parentCollection, parentId) {
var item = Items.findOne(itemId);
if (!item) return;
parentCollection = parentCollection || item.parent.collection;
parentId = parentId || item.parent.id;
if (Meteor.isServer) {
checkMovePermission(itemId, {collection: parentCollection, id: parentId});
}
//update the item provided the update will actually change something
if (
item.parent.collection !== parentCollection ||
item.parent.id !== parentId ||
item.enabled !== enable
){
Items.update(
itemId,
{$set: {
"parent.collection": parentCollection,
"parent.id": parentId,
enabled: enable,
}}
);
}
};
Meteor.methods({
moveItemToParent: function(itemId, parent) {
check(itemId, String);
check(parent, {collection: String, id: String});
moveItem(itemId, false, parent.collection, parent.id);
},
moveItemToCharacter: function(itemId, charId) {
check(itemId, String);
check(charId, String);
moveItem(itemId, false, "Characters", charId);
},
moveItemToContainer: function(itemId, containerId) {
check(itemId, String);
check(containerId, String);
moveItem(itemId, false, "Containers", containerId);
},
equipItem: function(itemId, charId){
check(itemId, String);
check(charId, String);
moveItem(itemId, true, "Characters", charId);
},
unequipItem: function(itemId, charId){
check(itemId, String);
check(charId, String);
moveItem(itemId, false, "Characters", charId);
},
splitItemToParent: function(itemId, moveQuantity, parent){
check(itemId, String);
check(moveQuantity, Number);
check(parent, {id: String, collection: String});
//get the item
var item = Items.findOne(itemId);
if (!item) return;
//don't bother moving nothing
if (moveQuantity <= 0 || item.quantity <= 0){
return;
}
//ensure we are only moving up to the current stack size
if (item.quantity < moveQuantity){
moveQuantity = this.quantity;
}
if (Meteor.isServer) {
checkMovePermission(itemId, parent);
}
//create a new item stack
var newStack = _.omit(EJSON.clone(item), "_id");
newStack.parent = parent;
newStack.quantity = moveQuantity;
//find out if we have an exact replica in the destination
var query = _.omit(newStack, ["parent", "quantity"]);
query["parent.collection"] = newStack.parent.collection;
query["parent.id"] = newStack.parent.id;
query._id = {$ne: itemId}; //make sure we don't join it to itself
var existingStack = Items.findOne(query);
if (existingStack){
//increase the existing stack's size
Items.update(
existingStack._id,
{$inc: {quantity: moveQuantity}}
);
} else {
//insert the new stack
Items.insert(newStack, function(err, id){
if (err) throw err;
//copy the children also
Meteor.call("cloneChildren", item._id, {collection: "Items", id: id});
});
}
//reduce the old stack's size
var oldQuantity = item.quantity - moveQuantity;
if (oldQuantity === 0){
Items.remove(itemId);
} else {
Items.update(itemId, {$set: {quantity: oldQuantity}});
}
},
});
Items.helpers({ Items.helpers({
totalValue: function(){ totalValue: function(){
return this.value * this.quantity; return this.value * this.quantity;
@@ -23,73 +178,21 @@ Items.helpers({
return this.weight * this.quantity; return this.weight * this.quantity;
}, },
pluralName: function(){ pluralName: function(){
if(this.plural && this.quantity !== 1){ if (this.plural && this.quantity !== 1){
return this.plural; return this.plural;
} else{ } else {
return this.name; return this.name;
} }
}, },
equip: function(characterId){
var charId = characterId || this.charId;
if(!charId || ! Characters.findOne(charId)) throw "Invalid character";
if(this.parent.collection === "Characters" && this.parent.id === charId && this.enabled) return;
Items.update(this._id, {$set: {"parent.collection": "Characters", "parent.id": charId, enabled: true}});
},
unequip: function(){
if(!this.enabled) return;
Items.update(this._id, {$set: {enabled: false}});
},
moveToContainer: function(containerId){
if( !containerId || !Containers.findOne(containerId) ) throw "Invalid container";
if(this.parent.collection === "Containers" && this.parent.id === containerId && !this.enabled) return;
Items.update(this._id, {$set: {"parent.collection": "Containers", "parent.id": containerId, enabled: false}});
},
moveToCharacter: function(characterId){
if(!characterId || ! Characters.findOne(characterId)) throw "Invalid character";
if(this.parent.collection === "Characters" && this.parent.id === characterId && !this.enabled) return;
Items.update(this._id, {$set: {"parent.collection": "Characters", "parent.id": characterId, charId: characterId, enabled: false}});
},
splitToParent: function(parent, moveQuantity){
check(parent, {id: String, collection: String});
check(moveQuantity, Number);
var parentCollection = Meteor.isClient? window[parent.collection] : global[parent.collection];
if(!parent.id || !parentCollection.findOne(parent.id)) throw "Invalid parent";
var oldStack = this;
//we can only move as much as we have, leaving 0 behind at worst
if(oldStack.quantity < moveQuantity) moveQuantity = oldStack.quantity;
var oldQuantity = oldStack.quantity - moveQuantity;
var newStack = _.pick(oldStack, Schemas.Item.objectKeys());
newStack.parent = parent;
newStack.quantity = moveQuantity;
var existingStack = Items.findOne(_.omit(newStack, "quantity"));
var updateStackSize = function(){
if(oldQuantity > 0){
Items.update(oldStack._id, {$set: {quantity: oldQuantity}});
} else {
Items.remove(oldStack._id);
}
};
if(existingStack){
Items.update(existingStack._id, {$inc: {quantity: moveQuantity}}, {}, function(){
updateStackSize();
});
}else{
Items.insert(newStack, function(err, id){
if(err) throw err;
updateStackSize();
//copy the children also
Meteor.call("cloneChildren", oldStack._id, {collection: "Items", id: id});
});
}
}
}); });
Items.before.update(function(userId, doc, fieldNames, modifier, options){ Items.before.update(function(userId, doc, fieldNames, modifier, options){
if( if (
modifier && modifier.$set && modifier.$set.enabled && //we are equipping this item modifier && modifier.$set && modifier.$set.enabled && //we are equipping this item
!(modifier.$set["parent.collection"] === "Characters" && modifier.$set["parent.id"]) //and we haven't specified a character to equip to !(
modifier.$set["parent.collection"] === "Characters" &&
modifier.$set["parent.id"]
) //and we haven"t specified a character to equip to
){ ){
//equip it to the current character //equip it to the current character
modifier.$set["parent.collection"] = "Characters"; modifier.$set["parent.collection"] = "Characters";
@@ -97,21 +200,21 @@ Items.before.update(function(userId, doc, fieldNames, modifier, options){
} }
}); });
Items.attachBehaviour('softRemovable'); Items.attachBehaviour("softRemovable");
makeChild(Items); //children of containers makeChild(Items); //children of containers
makeParent(Items, ['name', 'enabled']); //parents of effects and attacks makeParent(Items, ["name", "enabled"]); //parents of effects and attacks
Items.allow(CHARACTER_SUBSCHEMA_ALLOW); Items.allow(CHARACTER_SUBSCHEMA_ALLOW);
//give characters default items //give characters default items
Characters.after.insert(function (userId, char) { Characters.after.insert(function(userId, char) {
if(Meteor.isServer){ if (Meteor.isServer){
var containerId = Containers.insert({ var containerId = Containers.insert({
name: "Coin Pouch", name: "Coin Pouch",
charId: char._id, charId: char._id,
isCarried: true, isCarried: true,
description: "A sturdy pouch for coins", description: "A sturdy pouch for coins",
color: "d" color: "d",
}); });
Items.insert({ Items.insert({
name: "Gold piece", name: "Gold piece",
@@ -123,8 +226,8 @@ Characters.after.insert(function (userId, char) {
color: "n", color: "n",
parent: { parent: {
id: containerId, id: containerId,
collection: "Containers" collection: "Containers",
} },
}); });
Items.insert({ Items.insert({
name: "Silver piece", name: "Silver piece",
@@ -136,8 +239,8 @@ Characters.after.insert(function (userId, char) {
color: "q", color: "q",
parent: { parent: {
id: containerId, id: containerId,
collection: "Containers" collection: "Containers",
} },
}); });
Items.insert({ Items.insert({
name: "Copper piece", name: "Copper piece",
@@ -149,8 +252,8 @@ Characters.after.insert(function (userId, char) {
color: "s", color: "s",
parent: { parent: {
id: containerId, id: containerId,
collection: "Containers" collection: "Containers",
} },
}); });
} }
}); });

View File

@@ -0,0 +1,27 @@
ChangeLogs = new Mongo.Collection("changeLogs");
Schemas.ChangeLog = new SimpleSchema({
version: {
type: String,
},
changes: {
type: [String],
},
});
ChangeLogs.attachSchema(Schemas.ChangeLog);
ChangeLogs.allow({
insert: function(userId, doc) {
var user = Meteor.users.findOne(userId);
if (user) return _.contains(user.roles, "admin");
},
update: function(userId, doc, fields, modifier) {
var user = Meteor.users.findOne(userId);
if (user) return _.contains(user.roles, "admin");
},
remove: function(userId, doc) {
var user = Meteor.users.findOne(userId);
if (user) return _.contains(user.roles, "admin");
},
});

View File

@@ -0,0 +1,79 @@
Reports = new Mongo.Collection("reports");
Schemas.Report = new SimpleSchema({
owner: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
title: {
type: String,
trim: false,
optional: true,
},
description: {
type: String,
trim: false,
optional: true,
},
type: {
type: String,
allowedValues: ["bug", "change", "feature", "general"],
defaultValue: "bug",
},
//the immediate impact of doing this action (eg. -1 rages)
severity: {
type: Number,
defaultValue: 5,
min: 1,
max: 10,
},
metaData: {
type: Object,
blackbox: true,
},
});
Reports.attachSchema(Schemas.Report);
Meteor.methods({
insertReport: function(report) {
check(report, {
title: String,
description: String,
type: String,
severity: Number,
metaData: Object,
});
report.owner = this.userId;
var id = Reports.insert(report);
var user = Meteor.users.findOne(this.userId);
var sender = user &&
user.emails &&
user.emails[0] &&
user.emails[0].address ||
user.services &&
user.services.google &&
user.services.google.email ||
"reports@dicecloud.com";
var bodyText = "Report ID: " + id +
"\nSeverity: " + report.severity +
"\nType: " + report.type +
"\n\n" + report.description;
Email.send({
from: sender,
to: "stefan.zermatten@gmail.com",
subject: "DiceCloud feedback - " + report.title,
text: bodyText,
});
},
deleteReport: function(id) {
var user = Meteor.users.findOne(this.userId);
if (!_.contains(user.roles, "admin")){
throw new Meteor.Error(
"not admin",
"The user must be an administrator to delete feedback"
);
}
Reports.remove(id);
},
});

View File

@@ -1,44 +1,23 @@
Schema = {};
Schema.User = new SimpleSchema({
username: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/,
optional: true
},
emails: {
type: [Object],
// this must be optional if you also use other login services like facebook,
// but if you use only accounts-password, then it can be required
optional: true
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
services: {
type: Object,
optional: true,
blackbox: true
},
roles: {
type: [String],
optional: true
}
});
Meteor.users.attachSchema(Schema.User);
Meteor.users.allow({ Meteor.users.allow({
update: function (userId, doc, fields, modifier) { update: function(userId, doc, fields, modifier) {
return userId === doc._id && if (
fields.length === 1 && doc._id === userId &&
fields[0] === 'username'; _.contains(fields, "username") &&
_.contains(fields, "profile") &&
fields.length === 2 &&
_.keys(modifier).length === 1 &&
modifier.$set &&
modifier.$set["profile.username"] &&
modifier.$set.username &&
_.keys(modifier.$set).length === 2
){
var expectedUsername = modifier.$set["profile.username"];
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
if (modifier.$set.username !== expectedUsername){
return false;
}
var foundUser = Meteor.call("getUserId", expectedUsername);
return !foundUser || foundUser === userId;
}
} }
}); });

View File

@@ -1,71 +1,95 @@
Router.configure({ Router.configure({
loadingTemplate: 'loading', loadingTemplate: "loading",
layoutTemplate: 'layout' layoutTemplate: "layout",
}); });
Router.map( function () { Router.plugin("ensureSignedIn", {
/* only: [
this.route('home', { "profile",
path: '/', "characterList",
waitOn: function(){ ]
return Meteor.subscribe("characterList", Meteor.userId()); });
},
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}});
}
}
});*/ //add a home route and change characterList route
this.route('characterList', { Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
path: '/',
waitOn: function(){ Router.map(function() {
return Meteor.subscribe("characterList", Meteor.userId()); this.route("/", {
name: "home",
onAfterAction: function() {
document.title = appName;
}, },
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}});
}
}
}); });
this.route('characterSheet', { this.route("characterList", {
path: '/character/:_id', path: "/characterList",
waitOn: function(){
return subsManager.subscribe("characterList", Meteor.userId());
},
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}, sort: {name: 1}});
}
},
onAfterAction: function() {
document.title = appName;
},
});
this.route("characterSheet", {
path: "/character/:_id",
waitOn: function(){ waitOn: function(){
return [ return [
Meteor.subscribe("singleCharacter", this.params._id, Meteor.userId()), subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()),
]; ];
}, },
data: function() { data: function() {
var data = Characters.findOne({_id: this.params._id}, {fields: {_id: 1, name: 1, color: 1}}); var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data; return data;
} },
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name;
}
},
}); });
this.route('inventory', { this.route("loading", {
path: '/inventory/:_id', path: "/loading"
});
this.route("profile", {
path: "/account",
onAfterAction: function() {
document.title = appName + " Account";
},
});
this.route("/changelog", {
name: "changeLog",
waitOn: function() {
return [
subsManager.subscribe("changeLog"),
]
},
data: { data: {
containers: function() { changeLogs: function() {
var containers = Containers.find({owner: data._id}, {fields: {_id: 1}}); return ChangeLogs.find({}, {sort: {version: -1}});
return containers; }
}, },
onAfterAction: function() {
} document.title = appName;
},
}); });
this.route('item', { this.route("/guide", {
path: '/item/:_id', name: "guide",
data: function() { onAfterAction: function() {
var data = Items.findOne({_id: this.params._id}); document.title = appName;
return data; },
}
}); });
});
this.route('loading', {
path: '/loading'
});
this.route('profile', {
path: '/account'
});
});

View File

@@ -13,10 +13,9 @@
"tests" "tests"
], ],
"dependencies": { "dependencies": {
"polymer": "Polymer/polymer#~0.5.4", "polymer": "Polymer/polymer#~0.5.5",
"core-elements": "Polymer/core-elements#~0.5.4", "core-elements": "Polymer/core-elements#~0.5.5",
"paper-elements": "Polymer/paper-elements#~0.5.4", "paper-elements": "Polymer/paper-elements#~0.5.5"
"paper-fab-menu": "cwdoh/paper-fab-menu"
}, },
"resolutions": { "resolutions": {
"core-component-page": "^0.5.0", "core-component-page": "^0.5.0",

View File

@@ -12,10 +12,10 @@ this.GlobalUI = (function() {
return toast.show(); return toast.show();
}; };
GlobalUI.deletedToast = function(id, collection, itemName){ GlobalUI.deletedToast = function(id, collection, itemName) {
GlobalUI.toast({ GlobalUI.toast({
text: itemName? itemName + " deleted" : "Deleted item from" + collection, text: itemName ? itemName + " deleted" : "Deleted item from" + collection,
template: "undoToast", template: "undoToast",
data: { data: {
id: id, id: id,
collection: collection collection: collection
@@ -23,7 +23,7 @@ this.GlobalUI = (function() {
}); });
}; };
GlobalUI.setDialog = function(opts){ GlobalUI.setDialog = function(opts) {
this.dialog = $("[global-dialog]")[0]; this.dialog = $("[global-dialog]")[0];
Session.set("global.ui.dialogHeader", opts.heading); Session.set("global.ui.dialogHeader", opts.heading);
Session.set("global.ui.dialogData", opts.data); Session.set("global.ui.dialogData", opts.data);
@@ -58,7 +58,7 @@ this.GlobalUI = (function() {
Session.set("global.ui.detailShow", true); Session.set("global.ui.detailShow", true);
}; };
//if setting the detail rather than showing it, //if setting the detail rather than showing it,
//the template should contain the following in template.rendered //the template should contain the following in template.rendered
// //
//if (!this.alreadyRendered){ //if (!this.alreadyRendered){
@@ -69,30 +69,30 @@ this.GlobalUI = (function() {
Session.set("global.ui.detailData", opts.data); Session.set("global.ui.detailData", opts.data);
Session.set("global.ui.detailTemplate", opts.template); Session.set("global.ui.detailTemplate", opts.template);
Session.set("global.ui.detailHeroId", opts.heroId); Session.set("global.ui.detailHeroId", opts.heroId);
if(!!(window.history && window.history.pushState)){ if (window.history && window.history.pushState) {
history.replaceState({detail: "closed", opts: opts}, "Detail Dialog"); history.replaceState({detail: "closed", opts: opts}, "Detail Dialog");
history.pushState({detail: "opened", opts: opts}, "Detail Dialog"); history.pushState({detail: "opened", opts: opts}, "Detail Dialog");
} }
}; };
var throttleBack = _.throttle(function(){ var throttleBack = _.throttle(function() {
history.back(); history.back();
}, 800, {trailing: false}); }, 800, {trailing: false});
GlobalUI.closeDetail = function(){ GlobalUI.closeDetail = function() {
if(!!(window.history && window.history.pushState)){ if (!!(window.history && window.history.pushState)) {
throttleBack(); throttleBack();
} else{ } else {
Session.set("global.ui.detailShow", false); Session.set("global.ui.detailShow", false);
} }
}; };
GlobalUI.popStateHandler = function(e){ GlobalUI.popStateHandler = function(e) {
var state = e.originalEvent.state; var state = e.originalEvent.state;
if(state) { if (state) {
if(state.detail === "closed"){ if (state.detail === "closed") {
Session.set("global.ui.detailShow", false); Session.set("global.ui.detailShow", false);
} else if(state.detail === "opened"){ } else if (state.detail === "opened") {
var opts = state.opts; var opts = state.opts;
Session.set("global.ui.detailData", opts.data); Session.set("global.ui.detailData", opts.data);
Session.set("global.ui.detailTemplate", opts.template); Session.set("global.ui.detailTemplate", opts.template);
@@ -115,22 +115,22 @@ Template.layout.helpers({
globalDialogFullOnMobile: function() { globalDialogFullOnMobile: function() {
return Session.get("global.ui.dialogFullOnMobile"); return Session.get("global.ui.dialogFullOnMobile");
}, },
globalDialogHeader: function(){ globalDialogHeader: function() {
return Session.get("global.ui.dialogHeader"); return Session.get("global.ui.dialogHeader");
}, },
globalDetailSelected: function(){ globalDetailSelected: function() {
return Session.get("global.ui.detailShow") ? 1 : 0; return Session.get("global.ui.detailShow") ? 1 : 0;
}, },
globalDetailTemplate: function(){ globalDetailTemplate: function() {
return Session.get("global.ui.detailTemplate"); return Session.get("global.ui.detailTemplate");
}, },
globalDetailData: function(){ globalDetailData: function() {
return Session.get("global.ui.detailData"); return Session.get("global.ui.detailData");
}, },
globalToastTemplate: function(){ globalToastTemplate: function() {
return Session.get("global.ui.toastTemplate"); return Session.get("global.ui.toastTemplate");
}, },
globalToastData: function(){ globalToastData: function() {
return Session.get("global.ui.toastData"); return Session.get("global.ui.toastData");
} }
}); });
@@ -143,7 +143,7 @@ Template.layout.events({
}, },
"core-animated-pages-transition-end [detail-pages]": function(e) { "core-animated-pages-transition-end [detail-pages]": function(e) {
var detailOpened = Session.get("global.ui.detailShow"); var detailOpened = Session.get("global.ui.detailShow");
if(!detailOpened){ if (!detailOpened) {
Session.set("global.ui.detailData", null); Session.set("global.ui.detailData", null);
Session.set("global.ui.detailTemplate", null); Session.set("global.ui.detailTemplate", null);
Session.set("global.ui.detailHeroId", null); Session.set("global.ui.detailHeroId", null);
@@ -151,14 +151,14 @@ Template.layout.events({
}, },
"core-animated-pages-transition-prepare": function(e) { "core-animated-pages-transition-prepare": function(e) {
var detailOpened = Session.get("global.ui.detailShow"); var detailOpened = Session.get("global.ui.detailShow");
if(detailOpened) { if (detailOpened) {
//set up the transition //set up the transition
} else { } else {
//undo hack //undo hack
$("#mainContentSection").removeClass("fake-selected"); $("#mainContentSection").removeClass("fake-selected");
} }
}, },
"tap #screenDim": function(e){ "tap #screenDim": function(e) {
GlobalUI.closeDetail(); GlobalUI.closeDetail();
} }
}); });

View File

@@ -1,3 +1,3 @@
Template.registerHelper("canCast", function(){ Template.registerHelper("canCast", function() {
return Characters.find({_id: this._id, spells: {$size: 0}}).count() === 0; return Characters.find({_id: this._id, spells: {$size: 0}}).count() === 0;
}); });

View File

@@ -0,0 +1,10 @@
Template.registerHelper("canEditCharacter", function(charId) {
return canEditCharacter(charId);
});
canEditCharacter = function(charId) {
var char = Characters.findOne(charId)
var userId = Meteor.userId();
return char.owner === userId ||
_.contains(char.writers, userId);
};

View File

@@ -1,7 +1,11 @@
Template.registerHelper("colorClass", function(color){ Template.registerHelper("colorClass", function(color) {
return color? getColorClass(color) : getColorClass(this.color); if (color) {
return getColorClass(color);
} else if (this.color) {
return getColorClass(this.color);
}
}); });
Template.registerHelper("hexColor", function(color){ Template.registerHelper("hexColor", function(color) {
return getHexColor(color); return getHexColor(color);
}); });

View File

@@ -1,7 +1,9 @@
Template.registerHelper("detailHero", function(suffix, givenId){ Template.registerHelper("detailHero", function(suffix, givenId) {
var id = givenId || this._id; var id = givenId || this._id;
if(suffix) id += suffix; if (suffix) {
if ( Session.equals("global.ui.detailHeroId", id) ) { id += suffix;
}
if (Session.equals("global.ui.detailHeroId", id)) {
return "hero"; return "hero";
} }
}); });

View File

@@ -1,124 +0,0 @@
/*Template.registerHelper(function("effectList", */var disabled = function(charId, fieldName){
var obj = Characters.findOne(charId, {fields: {_id: 1}}).getField(fieldName);
var result = $("<div>");
if(_.has(obj, "conditional") && obj.conditional.length > 0){
_.each(obj.conditional, function(cond){
var c = $("<div>")
.append("* ")
.append(evaluateString(charId, cond.name));
result.append(c);
});
}
if(obj.base > 0){
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(obj.base))
.append("Base");
result.append(c);
}
if(_.has(obj, "proficiency") && obj.proficiency.length > 0){
var highestProf = {};
_.each(obj.proficiency, function(prof, i){
var value = evaluateEffect(charId, prof)
if(i === 0 || value > highestProf.value){
highestProf.value = value;
highestProf.name = prof.name;
highestProf.calculation = prof.calculation;
}
});
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(highestProf.value).append(" x Proficiency Bonus"))
.append(highestProf.name);
result.append(c);
}
if(_.has(obj, "add") && obj.add.length > 0){
_.each(obj.add, function(a){
var value = signedString(evaluateEffect(charId, a));
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(value))
.append(a.name);
result.append(c);
});
}
if(_.has(obj, "mul") && obj.mul.length > 0){
_.each(obj.mul, function(a){
var value = signedString(evaluateEffect(charId, a));
var c = $("<div>")
.append($("<span>").addClass("auditValue").append("&times;").append(value))
.append(a.name);
result.append(c);
});
}
if(_.has(obj, "min") && obj.min.length > 0){
var highestMin = {};
_.each(obj.min, function(m, i){
var value = evaluateEffect(charId, m)
if(i === 0 || value > highestMin.value){
highestMin.value = value;
highestMin.name = m.name;
highestMin.calculation = m.calculation;
}
});
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(highestMin.value).append(" Minimum"))
.append(highestMin.name);
result.append(c);
}
if(_.has(obj, "max") && obj.max.length > 0){
var lowestMax = {};
_.each(obj.max, function(m, i){
var value = evaluateEffect(charId, m)
if(i === 0 || value < lowestMax.value){
lowestMax.value = value;
lowestMax.name = m.name;
lowestMax.calculation = m.calculation;
}
});
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(lowestMax.value).append(" Maximum"))
.append(lowestMax.name);
result.append(c);
}
if(obj.base < 0){
var c = $("<div>")
.append($("<span>").addClass("auditValue").append(obj.base))
.append("Damage");
result.append(c);
}
if(_.has(obj, "advantage") && obj.advantage.length > 0){
_.each(obj.advantage, function(adv){
var c = $("<div>")
.append($("<span>").addClass("auditValue").append("Advantage"))
.append(adv.name);
result.append(c);
})
}
if(_.has(obj, "disadvantage") && obj.disadvantage.length > 0){
_.each(obj.disadvantage, function(disadv){
var c = $("<div>")
.append($("<span>").addClass("auditValue").append("Disadvantage"))
.append(disadv.name);
result.append(c);
})
}
if(_.has(obj, "fail") && obj.fail.length > 0){
_.each(obj.fail, function(f){
var c = $("<div>")
.append($("<span>").addClass("auditValue").append("Fail"))
.append(f.name);
result.append(c);
})
}
var string = result.html()
if (_.isString(string)) return Spacebars.SafeString(string);
return string;
};

View File

@@ -1,25 +1,25 @@
Template.registerHelper("evaluate", function(charId, string){ Template.registerHelper("evaluate", function(charId, string) {
return evaluate(charId, string); return evaluate(charId, string);
}); });
Template.registerHelper("evaluateSigned", function(charId, string){ Template.registerHelper("evaluateSigned", function(charId, string) {
var number = evaluate(charId, string); var number = evaluate(charId, string);
if(_.isFinite(number)){ if (_.isFinite(number)) {
return number > 0? "+" + number : "" + number; return number > 0 ? "+" + number : "" + number;
} else{ } else {
return number; return number;
} }
}); });
Template.registerHelper("evaluateSignedSpaced", function(charId, string){ Template.registerHelper("evaluateSignedSpaced", function(charId, string) {
var number = evaluate(charId, string); var number = evaluate(charId, string);
if(_.isFinite(number)){ if (_.isFinite(number)) {
return number > 0? "+ " + number : "- " + (-1 * number); return number > 0 ? "+ " + number : "- " + (-1 * number);
} else{ } else {
return number; return number;
} }
}); });
Template.registerHelper("evaluateString", function(charId, string){ Template.registerHelper("evaluateString", function(charId, string) {
return evaluateString(charId, string); return evaluateString(charId, string);
}); });

View File

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

View File

@@ -1,6 +1,6 @@
Template.registerHelper("round", function(value, decimalPlaces){ Template.registerHelper("round", function(value, decimalPlaces) {
decimalPlaces = +decimalPlaces || 2; decimalPlaces = +decimalPlaces || 2;
var num = +value; var num = +value;
var tens = Math.pow(10, decimalPlaces) var tens = Math.pow(10, decimalPlaces);
return Math.round(num * tens) / tens; return Math.round(num * tens) / tens;
}); });

View File

@@ -1,3 +1,3 @@
Template.registerHelper("session", function(key){ Template.registerHelper("session", function(key) {
return Session.get(key); return Session.get(key);
}); });

View File

@@ -1,3 +1,3 @@
Template.registerHelper("signedString", function(number){ Template.registerHelper("signedString", function(number) {
return number > 0? "+" + number : "" + number; return number >= 0 ? "+" + number : "" + number;
}); });

View File

@@ -1,24 +1,63 @@
Template.registerHelper("valueString", function(value){ Template.registerHelper("valueString", function(value) {
var intValue = Math.round(value * 100);
var cp = intValue % 10;
intValue -= cp;
cp = Math.round(cp);
sp = intValue % 100;
intValue -= sp;
sp = Math.round(sp / 10)
gp = Math.floor(value);
var resultArray = []; var resultArray = [];
//sp if (gp > 0) {
var gp = Math.floor(value); resultArray.push(gp + "gp");
if(gp > 0) resultArray.push(gp + "gp"); }
//sp if (sp > 0) {
var sp = Math.floor(10 * (value % 1)); resultArray.push(sp + "sp");
if(sp > 0) resultArray.push(sp + "sp"); }
//cp if (cp > 0) {
var cp = 10 * ((value * 10) % 1); resultArray.push(cp + "cp");
cp = Math.round(cp * 1000) / 1000; }
if(cp > 0) resultArray.push(cp + "cp");
//build string with correct spacing //build string with correct spacing
var result = ""; var result = "";
for(var i = 0; i < resultArray.length; i++){ for (var i = 0, l = resultArray.length; i < l; i++) {
//add a space between values //add a space between values
if(i !== 0){ if (i !== 0) {
result += " "; result += " ";
} }
result += resultArray[i]; result += resultArray[i];
} }
return result; return result;
}); });
Template.registerHelper("longValueString", function(value) {
var resultArray = [];
//sp
var gp = Math.floor(value);
if (gp > 0) {
resultArray.push(gp + "gp");
}
//sp
var sp = Math.floor(10 * (value % 1));
if (sp > 0 || resultArray.length) {
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");
}
//build string with correct spacing
var result = "";
for (var i = 0; i < resultArray.length; i++) {
//add a space between values
if (i !== 0) {
result += " ";
}
result += resultArray[i];
}
return result;
});

View File

@@ -0,0 +1,96 @@
@import "bourbon/bourbon";
$thickColumnWidth: 304px;
$thinColumnWidth: 240px;
//Column layouts of cards
.column-container {
@include column-fill(balance);
@include column-gap(8px);
@include column-width($thickColumnWidth);
padding: 8px;
&.thin-columns {
@include column-count(4);
@include column-width($thinColumnWidth);
}
}
//Cards
.card {
background: white;
border-radius: 2px;
.column-container & {
margin-bottom: 8px;
width: 100%;
//hack to stop flickering
-webkit-backface-visibility: hidden;
-webkit-transform: translateX(0);
//stop breaking over column divide
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
//Fixes extra margin at top of columns
display: inline-block;
}
.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,8 +70,8 @@
background-color: #9E9E9E; background-color: #9E9E9E;
} }
.blue-grey { .app-grey {
background-color: #607D8B; background-color: #424242;
} }
.white { .white {

View File

@@ -0,0 +1,37 @@
/*
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

@@ -0,0 +1,89 @@
@import "bourbon/bourbon";
@import "colors";
//apply a natural box layout model to all elements
*, *:before, *:after {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
root {
display: block;
}
body {
font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial;
margin: 0;
overflow-x: hidden;
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
p {
margin-bottom: 8px;
}
//Horizontal rule
hr {
background-color: #444;
opacity: 0.12;
border-width: 0;
color: #444;
height: 1px;
line-height: 0;
margin: 16px 0;
text-align: center;
}
//FABs
.floatyButton {
position: absolute;
bottom: 24px;
right: 24px;
}
//Buttons
paper-button {
color: #000;
color: rgba(0,0,0,0.87);
font-size: 14px;
font-weight: 400;
letter-spacing: 0.010;
text-transform: uppercase;
}
//Style shortcuts
.scroll-y {
overflow-y: auto;
}
.clickable, core-item, paper-tab {
cursor: pointer;
}
.pre-wrap, .prewrap{
white-space: pre-wrap;
}
.padded {
padding: 8px;
}
.fullwidth {
width: 100%;
}
.fab-buffer {
height: 100px;
}

View File

@@ -0,0 +1,20 @@
.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,21 @@
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,222 +0,0 @@
root {
display: block;
}
body {
font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial;
margin: 0;
overflow-x: hidden;
}
body.core-narrow {
padding: 8px;
}
body /deep/ core-menu {
overflow-x: hidden !important;
}
.calculatedValue {
color: #021C33;
font-weight: bold;
}
* {
box-sizing: border-box;
}
td {
padding: 0;
}
table {
border-spacing: 0;
}
hr {
background-color: #444;
opacity: 0.12;
border-width: 0;
color: #444;
height: 1px;
line-height: 0;
margin: 0 -16px;
text-align: center;
}
paper-button {
font-size: 14px;
font-weight: 400;
text-transform: uppercase;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010;
}
.listRow {
height: 32px;
}
.card {
margin-bottom: 8px;
/*hack to stop flickering*/
-webkit-backface-visibility: hidden;
-webkit-transform: translateX(0);
/*stop divs breaking over divide*/
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
/*Fixes extra margin at top of columns*/
display: inline-block;
width: 100%;
font-size: 14px;
border-radius: 2px;
background-color: white;
}
.card.double {
padding: 0;
}
.card paper-button {
font-size: 14px;
letter-spacing: 0.01em;
}
.cardHeader {
height: 48px;
padding: 0 16px 0 16px;
align-content: center;
font-size: 14px;
font-weight: 400;
color: rgba(0, 0, 0, 0.54);
}
.statCard, .clickable {
cursor: pointer;
}
.resourceCards {
padding: 4px 4px 0 4px;
margin-bottom: -4px;
}
.resourceCards .card {
width: 180px;
margin: 4px;
flex-grow: 1;
flex-shrink: 1;
}
.grey-background, body {
background-color: #E0E0E0;
}
.center {
text-align: center;
}
.screen-center {
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.scroll-y {
overflow-y: auto;
}
.fab-buffer {
height: 88px;
width: 100%;
order: 999;
}
*[hidden] {
visibility: hidden;
}
.hidden{
opacity: 0;
}
@media (max-width: 640px) {
html /deep/ paper-action-dialog[global-dialog] {
top: 0 !important;
left: 0 !important;
width: 100%;
height: 100%;
margin: 0;
border-radius: 0;
}
}
.floatyButton {
position: absolute;
bottom: 24px;
right: 24px;
}
paper-fab-menu /deep/ .container {
padding: 24px !important;
}
paper-slider {
margin-left: -8px;
}
.list-subhead {
color: rgba(0,0,0,0.54);
font-size: 14px;
height: 40px;
padding-left: 16px;
font-weight: 500;
}
.whiteTop {
border-bottom: rgba(0,0,0,0.12) solid 1px;
background: white;
padding: 16px;
position: relative;
border-radius: 2px 2px 0 0;
}
.whiteTop paper-icon-button {
margin: -8px;
}
.fullwidth {
width: 100%;
}
.padded {
padding: 16px;
}
.listPadded {
padding: 0 0 16px 0;
}
.rightPadded {
padding-right: 16px;
}
.bottomPadded {
padding-bottom: 16px;
}
.s {
padding: 0 0 16px 0;
}
.leftRound{
border-radius: 2px 0 0 2px;
}
.preline {
white-space: pre-line;
}

View File

@@ -18,7 +18,7 @@
letter-spacing: 0; letter-spacing: 0;
} }
.white-text .display1{ .white-text .display1, .white-text.display1{
color: rgba(255,255,255,0.54); color: rgba(255,255,255,0.54);
} }
@@ -109,6 +109,5 @@ html /deep/ .white-text{
.white54 { .white54 {
color: #eee; color: #eee;
color: rgba(255,255,255,0.54) color: rgba(255,255,255,0.54);
} }

View File

@@ -3,36 +3,26 @@
<div layout vertical flex> <div layout vertical flex>
<div layout horizontal> <div layout horizontal>
<!--attackBonus--> <!--attackBonus-->
<paper-input id="attackBonusInput" <paper-input class="attackBonusInput"
label="Attack Bonus" label="Attack Bonus"
floatinglabel floatinglabel
value={{attackBonus}} value={{attackBonus}}
flex></paper-input> flex></paper-input>
<!--details--> <!--details-->
<paper-input id="detailInput" <paper-input class="detailInput"
label="Details" label="Details"
floatinglabel floatinglabel
value={{details}}></paper-input> value={{details}}></paper-input>
</div> </div>
<div layout horizontal> <div layout horizontal>
<!--DamageType-->
<paper-dropdown-menu id="damageDiceDropdown" label="Damage Dice">
<paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{damageDice}}>
{{#each DAMAGE_DICE}}
<paper-item name={{this}} class="containerMenuItem">{{this}}</paper-item>
{{/each}}
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
<!--damageBonus--> <!--damageBonus-->
<paper-input id="damageInput" <paper-input class="damageInput"
label="Damage Bonus" label="Damage"
floatinglabel floatinglabel
value={{damageBonus}} value={{damage}}
flex></paper-input> flex></paper-input>
<!--DamageType--> <!--DamageType-->
<paper-dropdown-menu id="damageTypeDropdown" label="Damage Type"> <paper-dropdown-menu class="damageTypeDropdown" label="Damage Type">
<paper-dropdown layered class="dropdown"> <paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{damageType}}> <core-menu class="menu" selected={{damageType}}>
{{#each damageTypes}} {{#each damageTypes}}
@@ -44,6 +34,6 @@
</div> </div>
</div> </div>
<!--Delete Button--> <!--Delete Button-->
<paper-icon-button id="deleteAttack" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button> <paper-icon-button class="deleteAttack" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button>
</div> </div>
</template> </template>

View File

@@ -1,44 +1,50 @@
var damageTypes = ["bludgeoning", "piercing", "slashing", "acid", "cold", "fire", "force", "lightning", "necrotic", var damageTypes = [
"poison", "psychic", "radiant", "thunder"]; "bludgeoning",
"piercing",
"slashing",
"acid",
"cold",
"fire",
"force",
"lightning",
"necrotic",
"poison",
"psychic",
"radiant",
"thunder",
];
Template.attackEdit.events({ Template.attackEdit.events({
"tap #deleteAttack": function(event, instance){ "tap .deleteAttack": function(event, instance) {
Attacks.softRemoveNode(this._id); Attacks.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Attacks", "Attack"); GlobalUI.deletedToast(this._id, "Attacks", "Attack");
}, },
"change #attackBonusInput": function(event){ "change .attackBonusInput": function(event) {
var value = event.currentTarget.value; var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {attackBonus: value}}); Attacks.update(this._id, {$set: {attackBonus: value}});
}, },
"change #damageInput": function(event){ "change .damageInput": function(event) {
var value = event.currentTarget.value; var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {damageBonus: value}}); Attacks.update(this._id, {$set: {damage: value}});
}, },
"change #detailInput": function(event){ "change .detailInput": function(event) {
var value = event.currentTarget.value; var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {details: value}}); Attacks.update(this._id, {$set: {details: value}});
}, },
"core-select #damageTypeDropdown": function(event){ "core-select .damageTypeDropdown": function(event) {
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if(!detail.isSelected) return; if (!detail.isSelected) return;
var value = detail.item.getAttribute("name"); var value = detail.item.getAttribute("name");
if(value == this.damageType) return; if (value == this.damageType) return;
Attacks.update(this._id, {$set: {damageType: value}}); Attacks.update(this._id, {$set: {damageType: value}});
}, },
"core-select #damageDiceDropdown": function(event){
var detail = event.originalEvent.detail;
if(!detail.isSelected) return;
var value = detail.item.getAttribute("name");
if(value == this.damageDice) return;
Attacks.update(this._id, {$set: {damageDice: value}});
}
}); });
Template.attackEdit.helpers({ Template.attackEdit.helpers({
damageTypes: function(){ damageTypes: function() {
return damageTypes; return damageTypes;
}, },
DAMAGE_DICE: function(){ DAMAGE_DICE: function() {
return DAMAGE_DICE; return DAMAGE_DICE;
} },
}); });

View File

@@ -1,4 +1,4 @@
<!--needs to be given charId, parentId and type--> <!--needs to be given charId, parentId and parentCollection-->
<template name="attackEditList"> <template name="attackEditList">
{{#if attacks.count}} {{#if attacks.count}}
<hr style="margin: 16px 0 16px 0;"> <hr style="margin: 16px 0 16px 0;">

View File

@@ -1,19 +1,18 @@
Template.attackEditList.helpers({ Template.attackEditList.helpers({
attacks: function(){ attacks: function() {
var cursor = Attacks.find({"parent.id": this.parentId, type: this.type, charId: this.charId}); var cursor = Attacks.find({"parent.id": this.parentId, charId: this.charId});
return cursor; return cursor;
} }
}); });
Template.attackEditList.events({ Template.attackEditList.events({
"tap #addAttackButton": function(){ "tap #addAttackButton": function() {
Attacks.insert({ Attacks.insert({
charId: this.charId, charId: this.charId,
parent: { parent: {
id: this.parentId, id: this.parentId,
collection: this.parentCollection collection: this.parentCollection
}, }
type: this.type,
}); });
}, },
}); });

View File

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

View File

@@ -0,0 +1,11 @@
<template name="attacksViewList">
{{#if attacks.count}}
<hr style="margin: 16px 0 16px 0;">
<div class="attacks">
<h2 class="spaceAfter">Attacks</h2>
{{#each attacks}}
{{> attackView}}
{{/each}}
</div>
{{/if}}
</template>

View File

@@ -0,0 +1,5 @@
Template.attacksViewList.helpers({
attacks: function() {
return Attacks.find({"parent.id": this.parentId, charId: this.charId});
}
});

View File

@@ -0,0 +1,15 @@
<template name="buffDialog">
{{#with buff}}
{{#baseDialog title=name class=colorClass hideEdit=true}}
{{> buffDetails}}
{{/baseDialog}}
{{/with}}
</template>
<template name="buffDetails">
{{#if description}}
<div class="pre-wrap">{{evaluateString charId description}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
</template>

View File

@@ -0,0 +1,5 @@
Template.buffDialog.helpers({
buff: function(){
return Buffs.findOne(this.buffId);
},
});

View File

@@ -1,3 +1,27 @@
<template name="characterSettings"> <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>
{{/with}}
<paper-button id="doneButton" affirmative> Done </paper-button>
</template> </template>

View File

@@ -1,3 +1,26 @@
Template.characterSettings.events({ Template.characterSettings.helpers({
character: function() {
return Characters.findOne(this._id, {fields: {settings: 1}});
}
});
Template.characterSettings.events({
"change #variantEncumbrance": function(event, instance){
var value = instance.find("#variantEncumbrance").checked;
if (this.settings.useVariantEncumbrance !== value){
Characters.update(
this._id,
{$set: {"settings.useVariantEncumbrance": value}}
);
}
},
"change #hideSpellcasting": function(event, instance){
var value = instance.find("#hideSpellcasting").checked;
if (this.settings.hideSpellcasting !== value){
Characters.update(
this._id,
{$set: {"settings.hideSpellcasting": value}}
);
}
},
}); });

View File

@@ -1,23 +1,25 @@
Template.deleteCharacterConfirmation.onCreated(function(){ Template.deleteCharacterConfirmation.onCreated(function() {
this.canDelete = new ReactiveVar(false); this.canDelete = new ReactiveVar(false);
}); });
Template.deleteCharacterConfirmation.helpers({ Template.deleteCharacterConfirmation.helpers({
cantDelete: function(){ cantDelete: function() {
return !Template.instance().canDelete.get(); return !Template.instance().canDelete.get();
}, },
getStyle: function(){ getStyle: function() {
if(Template.instance().canDelete.get()) return "background: #d23f31; color: white;"; if (Template.instance().canDelete.get()) {
return "background: #d23f31; color: white;";
}
} }
}); });
Template.deleteCharacterConfirmation.events({ Template.deleteCharacterConfirmation.events({
"change #nameInput, input #nameInput": function(event, instance){ "change #nameInput, input #nameInput": function(event, instance) {
var canDel = instance.find("#nameInput").value === this.name; var canDel = instance.find("#nameInput").value === this.name;
instance.canDelete.set(canDel); instance.canDelete.set(canDel);
}, },
"tap #deleteButton": function(event, instance){ "tap #deleteButton": function(event, instance) {
if(instance.find("#nameInput").value === this.name){ if (instance.find("#nameInput").value === this.name) {
GlobalUI.closeDialog(); GlobalUI.closeDialog();
Router.go("/"); Router.go("/");
Characters.remove(this._id); Characters.remove(this._id);

View File

@@ -1,26 +1,54 @@
<template name="shareDialog"> <template name="shareDialog">
<div> <div style="width: 360px;">
<div> <div layout horizontal center>
<div class="subhead"> <div>Who can view this character: </div>
Can View <paper-dropdown-menu class="visibilityDropdown"
</div> label="Visibility">
{{#each readers}} <paper-dropdown layered class="dropdown">
{{this}}<br> <core-menu class="menu visibilityMenu" selected={{viewPermission}}>
{{/each}} <paper-item name="whitelist">Only people I share with</paper-item>
<div class="subhead"> <paper-item name="public">Anyone with link</paper-item>
Can Edit </core-menu>
</div> </paper-dropdown>
{{#each writers}} </paper-dropdown-menu>
{{this}}<br>
{{/each}}
</div> </div>
<paper-input id="userNameOrEmailInput" label="Username or email" floatinglabel></paper-input><br> <div>
{{#if userFindError}}<p style="color: red;">{{userFindError}}</p>{{/if}} {{#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-group id="accessLevelMenu" selected="read">
<paper-radio-button name="read" label="View Only"></paper-radio-button> <paper-radio-button name="read" label="View Only"></paper-radio-button>
<paper-radio-button name="write" label="Can Edit"></paper-radio-button> <paper-radio-button name="write" label="Can Edit"></paper-radio-button>
</paper-radio-group> </paper-radio-group>
<br>
<paper-button id="shareButton" class="red-button" raised disabled={{shareButtonDisabled}}>Share</paper-button>
</div> </div>
<paper-button id="doneButton" affirmative> Done </paper-button>
</template> </template>

View File

@@ -3,13 +3,17 @@ Template.shareDialog.onCreated(function(){
}); });
Template.shareDialog.helpers({ Template.shareDialog.helpers({
viewPermission: function() {
var char = Characters.findOne(this._id, {fields: {settings: 1}});
return char.settings.viewPermission || "whitelist";
},
readers: function(){ readers: function(){
var char = Characters.findOne(this._id, {fields: {readers: 1}}); var char = Characters.findOne(this._id, {fields: {readers: 1}});
return char && char.readers; return Meteor.users.find({_id: {$in: char.readers}});
}, },
writers: function(){ writers: function(){
var char = Characters.findOne(this._id, {fields: {writers: 1}}); var char = Characters.findOne(this._id, {fields: {writers: 1}});
return char && char.writers; return Meteor.users.find({_id: {$in: char.writers}});
}, },
shareButtonDisabled: function(){ shareButtonDisabled: function(){
return !Template.instance().userId.get(); return !Template.instance().userId.get();
@@ -18,17 +22,29 @@ Template.shareDialog.helpers({
if (!Template.instance().userId.get()){ if (!Template.instance().userId.get()){
return "User not found"; return "User not found";
} }
},
getUserName: function() {
return this.username || "user: " + this._id;
} }
}); });
Template.shareDialog.events({ Template.shareDialog.events({
"input #userNameOrEmailInput, change #userNameOrEmailInput": function(event, instance){ "core-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;
Characters.update(this._id, {$set: {"settings.viewPermission": value}});
},
"input #userNameOrEmailInput":
function(event, instance){
var userName = instance.find("#userNameOrEmailInput").value; var userName = instance.find("#userNameOrEmailInput").value;
instance.userId.set(undefined); instance.userId.set(undefined);
Meteor.call("getUserId", userName, function (err, result) { Meteor.call("getUserId", userName, function(err, result) {
if(err){ if (err){
console.error(err); console.error(err);
} else{ } else {
console.log(result); console.log(result);
instance.userId.set(result); instance.userId.set(result);
} }
@@ -37,19 +53,24 @@ Template.shareDialog.events({
"tap #shareButton": function(event, instance){ "tap #shareButton": function(event, instance){
var self = this; var self = this;
var permission = instance.find("#accessLevelMenu").selected; var permission = instance.find("#accessLevelMenu").selected;
if(!permission) throw "no permission set"; if (!permission) throw "no permission set";
var userId = instance.userId.get(); var userId = instance.userId.get();
if(!userId) return; if (!userId) return;
if(permission === "write"){ if (permission === "write"){
Characters.update(self._id, { Characters.update(self._id, {
$addToSet: {writers: userId}, $addToSet: {writers: userId},
$pull: {readers: userId} $pull: {readers: userId},
}); });
} else { } else {
Characters.update(self._id, { Characters.update(self._id, {
$addToSet: {readers: userId}, $addToSet: {readers: userId},
$pull: {writers: userId} $pull: {writers: userId},
}); });
} }
} },
"tap .deleteShare": function(event, instance) {
Characters.update(instance.data._id, {
$pull: {writers: this._id, readers: this._id}
});
},
}); });

View File

@@ -1,5 +1,4 @@
paper-tabs, core-toolbar { paper-tabs, core-toolbar {
background-color: #795548;
box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.2); box-shadow: 0px 3px 2px rgba(0, 0, 0, 0.2);
} }
@@ -21,18 +20,9 @@ paper-tabs ::shadow #ink {
paper-tabs.transparent-brown { paper-tabs.transparent-brown {
background-color: transparent; background-color: transparent;
color: #795548;
box-shadow: none; box-shadow: none;
} }
paper-tabs.transparent-brown::shadow #selectionBar {
background-color: #795548;
}
paper-tabs.transparent-brown paper-tab::shadow #ink {
color: #795548;
}
core-toolbar.medium-tall { core-toolbar.medium-tall {
height: 108px; height: 108px;
} }

View File

@@ -5,24 +5,35 @@
<div flex> <div flex>
{{name}} {{name}}
</div> </div>
<div> {{#if canEditCharacter _id}}
{{> colorDropdown}} <div>
</div> {{> colorDropdown}}
<paper-menu-button> </div>
<paper-icon-button icon="menu" noink></paper-icon-button> <paper-menu-button>
<paper-dropdown class="dropdown" halign="right"> <paper-icon-button icon="more-vert" noink></paper-icon-button>
<core-menu class="menu" style="color: black; color: rgba(0,0,0,0.87);"> <paper-dropdown class="dropdown" halign="right">
<paper-item id="deleteCharacter"><core-icon icon="delete"></core-icon>Delete</paper-item> <core-menu class="menu" style="color: black; color: rgba(0,0,0,0.87);">
<paper-item id="shareCharacter"><core-icon icon="social:share"></core-icon>Share</paper-item> <paper-item id="deleteCharacter">
</core-menu> <core-icon icon="delete"></core-icon>Delete
</paper-dropdown> </paper-item>
</paper-menu-button> <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> <div class="bottom fit" horizontal layout>
<paper-tabs flex horizontal center layout id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}"> <paper-tabs flex horizontal center layout id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
<paper-tab name="stats">Stats</paper-tab> <paper-tab name="stats">Stats</paper-tab>
<paper-tab name="features">Features</paper-tab> <paper-tab name="features">Features</paper-tab>
<paper-tab name="inventory">Inventory</paper-tab> <paper-tab name="inventory">Inventory</paper-tab>
{{#unless hideSpellcasting}}
<paper-tab name="spells">Spells</paper-tab> <paper-tab name="spells">Spells</paper-tab>
{{/unless}}
<paper-tab name="persona">Persona</paper-tab> <paper-tab name="persona">Persona</paper-tab>
<paper-tab name="journal">Journal</paper-tab> <paper-tab name="journal">Journal</paper-tab>
</paper-tabs> </paper-tabs>
@@ -33,7 +44,9 @@
<section flex name="stats">{{> stats}}</section> <section flex name="stats">{{> stats}}</section>
<section flex name="features">{{> features}}</section> <section flex name="features">{{> features}}</section>
<section flex name="inventory">{{> inventory}}</section> <section flex name="inventory">{{> inventory}}</section>
{{#unless hideSpellcasting}}
<section flex name="spells">{{> spells}}</section> <section flex name="spells">{{> spells}}</section>
{{/unless}}
<section flex name="persona">{{> persona}}</section> <section flex name="persona">{{> persona}}</section>
<section flex name="journal">{{> journal}}</section> <section flex name="journal">{{> journal}}</section>
</core-animated-pages> </core-animated-pages>

View File

@@ -1,6 +1,9 @@
Template.characterSheet.created = function(){ Template.characterSheet.onCreated(function() {
//default to the first tab
Session.setDefault(this.data._id + ".selectedTab", "stats"); Session.setDefault(this.data._id + ".selectedTab", "stats");
}; //watch this character and make sure their encumbrance is updated
trackEncumbranceConditions(this.data._id, this);
});
var setTab = function(charId, tab){ var setTab = function(charId, tab){
return Session.set(charId + ".selectedTab", tab); return Session.set(charId + ".selectedTab", tab);
@@ -13,7 +16,11 @@ var getTab = function(charId){
Template.characterSheet.helpers({ Template.characterSheet.helpers({
selectedTab: function(){ selectedTab: function(){
return getTab(this._id); return getTab(this._id);
} },
hideSpellcasting: function() {
var char = Characters.findOne(this._id);
return char && char.settings.hideSpellcasting;
},
}); });
Template.characterSheet.events({ Template.characterSheet.events({
@@ -40,4 +47,11 @@ Template.characterSheet.events({
template: "shareDialog", template: "shareDialog",
}); });
}, },
"tap #characterSettings": function(event, instance){
GlobalUI.showDialog({
heading: this.name + " Settings",
data: this,
template: "characterSettings",
});
},
}); });

View File

@@ -1,21 +1,13 @@
body /deep/ #statGroupDropDown { html /deep/ .operationDropDown {
width: 120px; width: 152px;
} }
body /deep/ #statDropDown { html /deep/ .statDropDown {
width: 120px; width: 152px;
} }
body /deep/ #operationDropDown { html /deep/ .damageMultiplierDropDown {
width: 100px; width: 152px;
}
body /deep/ #damageMultiplierDropDown {
width: 120px;
}
body /deep/ #proficiencyDropDown {
width: 120px;
} }
html /deep/ .effectEdit paper-input { html /deep/ .effectEdit paper-input {
@@ -24,6 +16,7 @@ html /deep/ .effectEdit paper-input {
} }
html /deep/ .effectEdit { html /deep/ .effectEdit {
height: 64px;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
} }

View File

@@ -1,27 +1,23 @@
<template name="effectEdit"> <template name="effectEdit">
<div class="effectEdit" layout horizontal center> <div class="effectEdit" layout horizontal center>
<paper-dropdown-menu class="statGroupDropDown" label="Stat Group" flex> <paper-dropdown-menu class="statDropDown"
<paper-dropdown layered class="dropdown"> label="Stat">
<core-menu class="menu statGroupMenu" selected={{selectedStatGroup}}> <paper-dropdown layered
{{#each statGroups}} class="dropdown">
<paper-item class="statGroupSelect" name={{this}}>{{this}}</paper-item> <core-menu class="menu statMenu" selected={{stat}}>
{{/each}} {{#each statGroups}}
</core-menu> <div style="font-weight: bold;
</paper-dropdown> margin-top: 16px;">{{this}}</div>
</paper-dropdown-menu> {{#each stats}}
{{#if stats}} <paper-item name={{stat}}>{{name}}</paper-item>
<paper-dropdown-menu class="statDropDown" label="Stat" flex> {{/each}}
<paper-dropdown layered class="dropdown">
<core-menu class="menu statMenu" selected={{stat}} on-tap="onStatMenuTap">
{{#each stats}}
<paper-item name={{stat}}>{{name}}</paper-item>
{{/each}} {{/each}}
</core-menu> </core-menu>
</paper-dropdown> </paper-dropdown>
</paper-dropdown-menu> </paper-dropdown-menu>
{{/if}}
{{#if operations}} {{#if operations}}
<paper-dropdown-menu class="operationDropDown" label="Operation" flex> <paper-dropdown-menu class="operationDropDown"
label="Operation">
<paper-dropdown layered class="dropdown"> <paper-dropdown layered class="dropdown">
<core-menu class="menu operationMenu" selected={{operation}}> <core-menu class="menu operationMenu" selected={{operation}}>
{{#each operations}} {{#each operations}}
@@ -31,36 +27,39 @@
</paper-dropdown> </paper-dropdown>
</paper-dropdown-menu> </paper-dropdown-menu>
{{/if}} {{/if}}
{{> Template.dynamic template=effectValueTemplate}} {{#if effectValueTemplate}}
<paper-icon-button class="deleteEffect" role="button" tabindex="0" icon="delete" aria-label="Delete"></paper-icon-button> {{> Template.dynamic template=effectValueTemplate}}
{{else}}
<div flex></div>
{{/if}}
<paper-icon-button class="deleteEffect"
icon="delete">
</paper-icon-button>
<br> <br>
</div> </div>
</template> </template>
<template name="regularEffectValue"> <template name="regularEffectValue">
<paper-input class="effectValueInput" label="Value" floatinglabel value={{effectValue}} flex></paper-input> <paper-input class="effectValueInput"
label="Value"
floatinglabel
value={{effectValue}}
flex>
</paper-input>
</template> </template>
<template name="multiplierEffectValue"> <template name="multiplierEffectValue">
<paper-dropdown-menu class="damageMultiplierDropDown" label="Damage Multiplier" flex> <paper-dropdown-menu class="damageMultiplierDropDown"
<paper-dropdown layered class="dropdown"> label="Damage Multiplier">
<core-menu class="menu multiplierMenu" selected={{value}}> <paper-dropdown layered
class="dropdown">
<core-menu class="menu multiplierMenu"
selected={{value}}>
<paper-item name="0.5">Resistance</paper-item> <paper-item name="0.5">Resistance</paper-item>
<paper-item name="2">Vulnerability</paper-item> <paper-item name="2">Vulnerability</paper-item>
<paper-item name="0">Immunity</paper-item> <paper-item name="0">Immunity</paper-item>
</core-menu> </core-menu>
</paper-dropdown> </paper-dropdown>
</paper-dropdown-menu> </paper-dropdown-menu>
<div flex></div>
</template> </template>
<template name="proficiencyEffectValue">
<paper-dropdown-menu class="proficiencyDropDown" label="Proficiency" flex>
<paper-dropdown layered class="dropdown">
<core-menu class="menu proficiencyMenu" selected={{value}}>
<paper-item name="1">Proficient</paper-item>
<paper-item name="0.5">Half Prof. Bonus</paper-item>
<paper-item name="2">Double Prof. Bonus</paper-item>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
</template>

View File

@@ -82,7 +82,6 @@ var attributeOperations = [
{name: "Max", operation: "max"} {name: "Max", operation: "max"}
]; ];
var skillOperations = [ var skillOperations = [
{name: "Proficiency", operation: "proficiency"},
{name: "Add", operation: "add"}, {name: "Add", operation: "add"},
{name: "Multiply", operation: "mul"}, {name: "Multiply", operation: "mul"},
{name: "Min", operation: "min"}, {name: "Min", operation: "min"},
@@ -94,26 +93,19 @@ var skillOperations = [
{name: "Conditional Benefit", operation: "conditional"} {name: "Conditional Benefit", operation: "conditional"}
]; ];
Template.effectEdit.created = function(){
var statGroup = statsDict[this.data.stat] && statsDict[this.data.stat].group;
this.selectedStatGroup = new ReactiveVar(statGroup);
};
Template.effectEdit.helpers({ Template.effectEdit.helpers({
selectedStatGroup: function(){
return Template.instance().selectedStatGroup.get();
},
statGroups: function(){ statGroups: function(){
return statGroupNames; return statGroupNames;
}, },
stats: function(){ stats: function(){
var group = Template.instance().selectedStatGroup.get(); var group = this;
return statGroups[group]; return statGroups[group];
}, },
operations: function(){ operations: function(){
var group = Template.instance().selectedStatGroup.get(); var stat = statsDict[this.stat];
if(group === "Weakness/Resistance") return null; var group = stat && stat.group;
if(group === "Saving Throws" || group === "Skills"){ if (group === "Weakness/Resistance") return null;
if (group === "Saving Throws" || group === "Skills"){
return skillOperations; return skillOperations;
} else { } else {
return attributeOperations; return attributeOperations;
@@ -121,15 +113,14 @@ Template.effectEdit.helpers({
}, },
effectValueTemplate: function(){ effectValueTemplate: function(){
//resistance/vulnerability template //resistance/vulnerability template
var group = Template.instance().selectedStatGroup.get(); var stat = statsDict[this.stat];
if(group === "Weakness/Resistance") return "multiplierEffectValue"; var group = stat && stat.group;
if (group === "Weakness/Resistance") return "multiplierEffectValue";
var op = this.operation; var op = this.operation;
if(!op) return null; if (!op) return null;
//operations that don't need templates //operations that don't need templates
if(op === "advantage" || op === "disadvantage" || op === "fail") return null; if (op === "advantage" || op === "disadvantage" || op === "fail") return null;
//proficiency template
if(op === "proficiency") return "proficiencyEffectValue";
//default template //default template
return "regularEffectValue"; return "regularEffectValue";
@@ -147,54 +138,35 @@ Template.effectEdit.events({
Effects.softRemoveNode(this._id); Effects.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Effects", "Effect"); GlobalUI.deletedToast(this._id, "Effects", "Effect");
}, },
"core-select .statGroupDropDown": function(event, instance){
var detail = event.originalEvent.detail;
if(!detail.isSelected) return;
var groupName = detail.item.getAttribute("name");
var oldName = Template.instance().selectedStatGroup.get();
if(oldName != groupName){
instance.selectedStatGroup.set(groupName);
if(groupName === "Skills" || groupName === "Saving Throws"){
Effects.update(this._id, {$set: {operation: "proficiency", value: 1, calculation: ""}, $unset: {stat: ""} });
} else if(groupName === "Weakness/Resistance"){
Effects.update(this._id, {$set: {value: 0.5, calculation: "", operation: "mul"}, $unset: {stat: ""} });
} else {
Effects.update(this._id, { $set: {operation: "add"}, $unset: {stat: "", value: "", calculation: ""} });
}
}
},
"core-select .statDropDown": function(event){ "core-select .statDropDown": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if(!detail.isSelected) return; if (!detail.isSelected) return;
var statName = detail.item.getAttribute("name"); var statName = detail.item.getAttribute("name");
if (statName == this.stat) return; if (statName == this.stat) return;
Effects.update(this._id, {$set: {stat: statName}}); Effects.update(this._id, {$set: {stat: statName}});
}, },
"core-select .operationDropDown": function(event){ "core-select .operationDropDown": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if(!detail.isSelected) return; if (!detail.isSelected) return;
var opName = detail.item.getAttribute("name"); var opName = detail.item.getAttribute("name");
if (opName == this.operation) return; if (opName == this.operation) return;
Effects.update(this._id, {$set: {operation: opName}}); Effects.update(this._id, {$set: {operation: opName}});
}, },
"core-select .damageMultiplierDropDown": function(event){ "core-select .damageMultiplierDropDown": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if(!detail.isSelected) return; if (!detail.isSelected) return;
var value = +detail.item.getAttribute("name"); var value = +detail.item.getAttribute("name");
if (value == this.value) return; if (value == this.value) return;
Effects.update(this._id, {$set: {value: value, calculation: "", operation: "mul"}}); Effects.update(this._id, {$set: {
}, value: value,
"core-select .proficiencyDropDown": function(event){ calculation: "",
var detail = event.originalEvent.detail; operation: "mul"
if(!detail.isSelected) return; }});
var value = +detail.item.getAttribute("name");
if (value == this.value) return;
Effects.update(this._id, {$set: {value: value, calculation: ""}});
}, },
"change .effectValueInput": function(event){ "change .effectValueInput": function(event){
var value = event.currentTarget.value; var value = event.currentTarget.value;
var numValue = +value; var numValue = +value;
if(_.isFinite(numValue)){ if (_.isFinite(numValue)){
if (this.value === numValue) return; if (this.value === numValue) return;
Effects.update(this._id, {$set: {value: numValue, calculation: ""}}); Effects.update(this._id, {$set: {value: numValue, calculation: ""}});
} else if (_.isString(value)){ } else if (_.isString(value)){

View File

@@ -1,12 +1,6 @@
<template name="effectView"> <template name="effectView">
<div class="effectView" layout horizontal center> <tr>
{{#with statName}} <td>{{statName}}</td>
<div flex>{{statName}}</div> <td>{{operationName}}{{statValue}}</td>
{{else}} </tr>
<div flex>{{sourceName}}</div>
{{/with}}
<div>{{operationName}}</div>
<div>{{statValue}}</div>
<br>
</div>
</template> </template>

View File

@@ -6,35 +6,35 @@ var stats = {
"intelligence":{"name":"Intelligence"}, "intelligence":{"name":"Intelligence"},
"wisdom":{"name":"Wisdom"}, "wisdom":{"name":"Wisdom"},
"charisma":{"name":"Charisma"}, "charisma":{"name":"Charisma"},
"strengthSave":{"name":"Strength Save",}, "strengthSave":{"name":"Strength Save"},
"dexteritySave":{"name":"Dexterity Save",}, "dexteritySave":{"name":"Dexterity Save"},
"constitutionSave":{"name":"Constitution Save",}, "constitutionSave":{"name":"Constitution Save"},
"intelligenceSave":{"name":"Intelligence Save",}, "intelligenceSave":{"name":"Intelligence Save"},
"wisdomSave":{"name":"Wisdom Save",}, "wisdomSave":{"name":"Wisdom Save"},
"charismaSave":{"name":"Charisma Save",}, "charismaSave":{"name":"Charisma Save"},
"acrobatics":{"name":"Acrobatics",}, "acrobatics":{"name":"Acrobatics"},
"animalHandling":{"name":"Animal Handling",}, "animalHandling":{"name":"Animal Handling"},
"arcana":{"name":"Arcana",}, "arcana":{"name":"Arcana"},
"athletics":{"name":"Athletics",}, "athletics":{"name":"Athletics"},
"deception":{"name":"Deception",}, "deception":{"name":"Deception"},
"history":{"name":"History",}, "history":{"name":"History"},
"insight":{"name":"Insight",}, "insight":{"name":"Insight"},
"intimidation":{"name":"Intimidation",}, "intimidation":{"name":"Intimidation"},
"investigation":{"name":"Investigation",}, "investigation":{"name":"Investigation"},
"medicine":{"name":"Medicine",}, "medicine":{"name":"Medicine"},
"nature":{"name":"Nature",}, "nature":{"name":"Nature"},
"perception":{"name":"Perception",}, "perception":{"name":"Perception"},
"performance":{"name":"Performance",}, "performance":{"name":"Performance"},
"persuasion":{"name":"Persuasion",}, "persuasion":{"name":"Persuasion"},
"religion":{"name":"Religion",}, "religion":{"name":"Religion"},
"sleightOfHand":{"name":"Sleight of Hand",}, "sleightOfHand":{"name":"Sleight of Hand"},
"stealth":{"name":"Stealth",}, "stealth":{"name":"Stealth"},
"survival":{"name":"Survival",}, "survival":{"name":"Survival"},
"initiative":{"name":"Initiative",}, "initiative":{"name":"Initiative"},
"hitPoints":{"name":"Hit Points"}, "hitPoints":{"name":"Hit Points"},
"armor":{"name":"Armor"}, "armor":{"name":"Armor"},
"dexterityArmor":{"name":"Dexterity Armor Bonus"} "dexterityArmor":{"name":"Dexterity Armor Bonus"},
,"speed":{"name":"Speed"}, "speed":{"name":"Speed"},
"proficiencyBonus":{"name":"Proficiency Bonus"}, "proficiencyBonus":{"name":"Proficiency Bonus"},
"ki":{"name":"Ki Points"}, "ki":{"name":"Ki Points"},
"sorceryPoints":{"name":"Sorcery Points"}, "sorceryPoints":{"name":"Sorcery Points"},
@@ -55,40 +55,63 @@ var stats = {
"d8HitDice":{"name":"d8 Hit Dice"}, "d8HitDice":{"name":"d8 Hit Dice"},
"d10HitDice":{"name":"d10 Hit Dice"}, "d10HitDice":{"name":"d10 Hit Dice"},
"d12HitDice":{"name":"d12 Hit Dice"}, "d12HitDice":{"name":"d12 Hit Dice"},
"acidMultiplier":{"name":"Acid", "group": "Weakness/Resistance"}, "acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
"bludgeoningMultiplier":{"name":"Bludgeoning", "group": "Weakness/Resistance"}, "bludgeoningMultiplier":{
"coldMultiplier":{"name":"Cold", "group": "Weakness/Resistance"}, "name":"Bludgeoning damage", "group": "Weakness/Resistance",
"fireMultiplier":{"name":"Fire", "group": "Weakness/Resistance"}, },
"forceMultiplier":{"name":"Force", "group": "Weakness/Resistance"}, "coldMultiplier":{
"lightningMultiplier":{"name":"Lightning", "group": "Weakness/Resistance"}, "name":"Cold damage", "group": "Weakness/Resistance",
"necroticMultiplier":{"name":"Necrotic", "group": "Weakness/Resistance"}, },
"piercingMultiplier":{"name":"Piercing", "group": "Weakness/Resistance"}, "fireMultiplier":{
"poisonMultiplier":{"name":"Poison", "group": "Weakness/Resistance"}, "name":"Fire damage", "group": "Weakness/Resistance",
"psychicMultiplier":{"name":"Psychic", "group": "Weakness/Resistance"}, },
"radiantMultiplier":{"name":"Radiant", "group": "Weakness/Resistance"}, "forceMultiplier":{
"slashingMultiplier":{"name":"Slashing", "group": "Weakness/Resistance"}, "name":"Force damage", "group": "Weakness/Resistance",
"thunderMultiplier":{"name":"Thunder", "group": "Weakness/Resistance"} },
"lightningMultiplier":{
"name":"Lightning damage", "group": "Weakness/Resistance",
},
"necroticMultiplier":{
"name":"Necrotic damage", "group": "Weakness/Resistance",
},
"piercingMultiplier":{
"name":"Piercing damage", "group": "Weakness/Resistance",
},
"poisonMultiplier":{
"name":"Poison damage", "group": "Weakness/Resistance",
},
"psychicMultiplier":{
"name":"Psychic damage", "group": "Weakness/Resistance",
},
"radiantMultiplier":{
"name":"Radiant damage", "group": "Weakness/Resistance",
},
"slashingMultiplier":{
"name":"Slashing damage", "group": "Weakness/Resistance",
},
"thunderMultiplier":{
"name":"Thunder damage", "group": "Weakness/Resistance",
},
}; };
var operations = { var operations = {
base: {name: "Base Value"}, base: {name: "Base Value: "},
proficiency: {name: "Proficiency"}, proficiency: {name: "Proficiency"},
add: {name: "Add"}, add: {name: "+"},
mul: {name: "Multiply"}, mul: {name: "x"},
min: {name: "Min"}, min: {name: "Min: "},
max: {name: "Max"}, max: {name: "Max: "},
advantage: {name: "Advantage"}, advantage: {name: "Advantage"},
disadvantage: {name: "Disadvantage"}, disadvantage: {name: "Disadvantage"},
passiveAdd: {name: "Passive Bonus"}, passiveAdd: {name: "Passive Bonus: "},
fail: {name: "Automatically Fail"}, fail: {name: "Automatically Fail"},
conditional: {name: "Conditional Benefit"}
}; };
Template.effectView.helpers({ Template.effectView.helpers({
sourceName: function(){ sourceName: function(){
var id = this.parent.id; var id = this.parent.id;
if(!id) return; if (!id) return;
switch(this.parent.collection){ switch (this.parent.collection){
case "Features": case "Features":
return "Feature - " + Features.findOne(id, {fields: {name: 1}}).name; return "Feature - " + Features.findOne(id, {fields: {name: 1}}).name;
case "Classes": case "Classes":
@@ -100,34 +123,46 @@ Template.effectView.helpers({
case "Characters": case "Characters":
return Characters.findOne(this.charId, {fields: {race: 1}}).race; return Characters.findOne(this.charId, {fields: {race: 1}}).race;
default: default:
return "Inate" return "Inate";
} }
}, },
statName: function(){ statName: function(){
return stats[this.stat] && stats[this.stat].name || "No Stat" return stats[this.stat] && stats[this.stat].name || "No Stat";
}, },
operationName: function(){ operationName: function(){
if(this.operation === "proficiency") return null; if (this.operation === "proficiency" ||
if(stats[this.stat].group === "Weakness/Resistance") return null; this.operation === "conditional") return null;
return operations[this.operation] && operations[this.operation].name || "No Operation" if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance")
return null;
if (this.operation === "add" && evaluateEffect(this.charId, this) < 0)
return null;
return operations[this.operation] &&
operations[this.operation].name || "No Operation";
}, },
statValue: function(){ statValue: function(){
if(this.operation === "advantage" || if (this.operation === "advantage" ||
this.operation === "disadvantage" || this.operation === "disadvantage" ||
this.operation === "fail" || this.operation === "fail"){
this.operation === "conditional"){
return null; return null;
} }
if(this.operation === "proficiency"){ if (this.operation === "proficiency"){
if(this.value == 0.5 || this.calculation == 0.5) return "Half Proficiency"; if (this.value == 0.5 || this.calculation == 0.5)
if(this.value == 1 || this.calculation == 1) return "Proficiency"; return "Half Proficiency";
if(this.value == 2 || this.calculation == 2) return "Double Proficiency"; if (this.value == 1 || this.calculation == 1)
return "Proficiency";
if (this.value == 2 || this.calculation == 2)
return "Double Proficiency";
} }
if(stats[this.stat].group === "Weakness/Resistance"){ if (this.operation === "conditional"){
if(this.value == 0.5 || this.calculation == 0.5) return "Resistance"; return this.calculation || this.value;
if(this.value == 2 || this.calculation == 2) return "Vulnerability";
if(this.value == 0 || this.calculation == 0) return "Immunity";
} }
if (stats[this.stat] && stats[this.stat].group === "Weakness/Resistance"){
if (this.value === 0.5) return "Resistance";
if (this.value === 2) return "Vulnerability";
if (this.value === 0) return "Immunity";
}
var value = evaluateEffect(this.charId, this);
if (_.isNumber(value)) return value;
return this.calculation || this.value; return this.calculation || this.value;
} },
}); });

View File

@@ -1,7 +1,7 @@
<!--needs to be given charId, parentId, parentCollection and type--> <!--needs to be given charId, parentId and parentCollection-->
<template name="effectsEditList"> <template name="effectsEditList">
{{#if effects.count}} {{#if effects.count}}
<hr style="margin: 16px 0 16px 0;"> <hr class="vertMargin">
<div id="effects"> <div id="effects">
<h2>Effects</h2> <h2>Effects</h2>
{{#each effects}} {{#each effects}}

View File

@@ -1,13 +1,21 @@
Template.effectsEditList.helpers({ Template.effectsEditList.helpers({
effects: function(){ effects: function(){
var cursor = Effects.find({"parent.id": this.parentId, "parent.collection": this.parentCollection, type: this.type}); var selector = {
"parent.id": this.parentId,
"parent.collection": this.parentCollection,
"charId": this.charId,
};
if (this.parentGroup){
selector["parent.group"] = this.parentGroup;
}
var cursor = Effects.find(selector);
return cursor; return cursor;
} }
}); });
Template.effectsEditList.events({ Template.effectsEditList.events({
"tap #addEffectButton": function(){ "tap #addEffectButton": function(){
if ( !_.isBoolean(this.enabled) ) { if (!_.isBoolean(this.enabled)) {
this.enabled = true; this.enabled = true;
} }
Effects.insert({ Effects.insert({
@@ -15,11 +23,11 @@ Template.effectsEditList.events({
charId: this.charId, charId: this.charId,
parent: { parent: {
id: this.parentId, id: this.parentId,
collection: this.parentCollection collection: this.parentCollection,
group: this.parentGroup,
}, },
operation: "add", operation: "add",
type: this.type, enabled: this.enabled,
enabled: this.enabled
}); });
}, },
}); });

View File

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

View File

@@ -1,10 +1,12 @@
Template.effectsViewList.helpers({ Template.effectsViewList.helpers({
effects: function(){ effects: function(){
if(this.parentId){ var selector = {
return Effects.find({"parent.id": this.parentId, type: this.type, charId: this.charId}, {fields: {parent: 0}}); "parent.id": this.parentId,
} else if(this.stat){ "charId": this.charId
return Effects.find({charId: this.charId, stat: this.stat}); };
if (this.parentGroup){
selector["parent.group"] = this.parentGroup;
} }
return Effects.find(selector, {fields: {parent: 0}});
} }
}); });

View File

@@ -1,40 +1,85 @@
<template name="featureDialog"> <template name="featureDialog">
{{#with feature}} {{#with feature}}
{{#baseDialog title=name class=colorClass}} {{#baseDialog title=name class=colorClass startEditing=../startEditing}}
<!--name--> {{> featureDetails}}
<paper-input id="featureNameInput" label="Name" floatinglabel value={{name}}></paper-input> {{else}}
<hr style="margin: 16px 0 16px 0;"> {{> featureEdit}}
<!--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>
<hr style="margin: 16px 0 16px 0;">
<div layout horizontal center style="height: 60px;">
<div class="caption">Limit Uses</div>
<paper-toggle-button id="limitUseCheck"
checked={{usesSet}}
role="button"
aria-pressed="false"
tabindex="0"
touch-action="pan-y"
style="margin-left: 8px; margin-right:8px;">
</paper-toggle-button>
{{#if usesSet}}
<paper-input id="usesInput" label="Uses" floatinglabel value={{uses}}></paper-input>
{{/if}}
<paper-dropdown-menu id="enabledDropdown" label="Enable Feature">
<paper-dropdown layered class="dropdown">
<core-menu id="enabledMenu" class="menu" selected={{enabled}} 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>
</paper-dropdown-menu>
</div>
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId type="feature" name=name enabled=isEnabled}}
{{/baseDialog}} {{/baseDialog}}
{{/with}} {{/with}}
</template> </template>
<template name="featureDetails">
{{#if or canEnable hasUses}}
<div layout horizontal center justified wrap>
{{#if canEnable}}
<div>enabled:</div>
<paper-checkbox class="sideMargin" checked={{enabled}}></paper-checkbox>
{{/if}}
{{#if hasUses}}
<div class="subhead" style="margin-right: 16px">
Uses: {{usesLeft}}/{{usesValue}}
</div>
{{/if}}
{{#if hasUses}}
<div layout horizontal>
<paper-button class="useFeature" disabled={{noUsesLeft}}>Use</paper-button>
<paper-button class="resetFeature" disabled={{usesFull}}>Reset</paper-button>
</div>
{{/if}}
</div>
<hr class="vertMargin">
{{/if}}
{{#if description}}
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> proficiencyViewList charId=charId parentId=_id}}
</template>
<template name="featureEdit">
<!--name-->
<paper-input id="featureNameInput" class="fullwidth" label="Name" floatinglabel 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>
</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>
{{#if usesSet}}
<paper-input flex id="usesInput" label="Uses" floatinglabel value={{uses}}></paper-input>
{{/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>
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
</template>

View File

@@ -1,3 +1,9 @@
Template.featureDialog.helpers({
feature: function(){
return Features.findOne(this.featureId);
},
});
Template.featureDialog.events({ Template.featureDialog.events({
"color-change": function(event, instance){ "color-change": function(event, instance){
Features.update(instance.data.featureId, {$set: {color: event.color}}); Features.update(instance.data.featureId, {$set: {color: event.color}});
@@ -7,6 +13,61 @@ Template.featureDialog.events({
GlobalUI.deletedToast(instance.data.featureId, "Features", "Feature"); GlobalUI.deletedToast(instance.data.featureId, "Features", "Feature");
GlobalUI.closeDetail(); GlobalUI.closeDetail();
}, },
});
Template.featureDetails.helpers({
or: function(a, b){
return a || b;
},
hasUses: function(){
return this.usesValue() > 0;
},
noUsesLeft: function(){
return this.usesLeft() <= 0;
},
usesFull: function(){
return this.usesLeft() >= this.usesValue();
},
});
Template.featureDetails.events({
"tap .useFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$inc: {used: 1}});
},
"tap .resetFeature": function(event){
var featureId = this._id;
Features.update(featureId, {$set: {used: 0}});
},
"change .enabledCheckbox": function(event){
var enabled = !this.enabled;
Features.update(this._id, {$set: {enabled: enabled}});
},
});
Template.featureEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.featureEdit.helpers({
usesSet: function(){
return _.isString(this.uses);
},
enabledSelection: function(){
if (this.enabled){
if (this.alwaysEnabled){
return "alwaysEnabled";
} else {
return "enabled";
}
} else if (this.enabled === false){ //make sure it is false, not just falsey
return "disabled";
}
},
});
Template.featureEdit.events({
"change #featureNameInput": function(event){ "change #featureNameInput": function(event){
var name = Template.instance().find("#featureNameInput").value; var name = Template.instance().find("#featureNameInput").value;
Features.update(this._id, {$set: {name: name}}); Features.update(this._id, {$set: {name: name}});
@@ -18,7 +79,7 @@ Template.featureDialog.events({
"change #limitUseCheck": function(event){ "change #limitUseCheck": function(event){
var currentUses = this.uses; var currentUses = this.uses;
var featureId = this._id; var featureId = this._id;
if( event.target.checked && !_.isString(currentUses) ){ 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)){ } else if (!event.target.checked && _.isString(currentUses)){
Features.update(featureId, {$unset: {uses: ""}}); Features.update(featureId, {$unset: {uses: ""}});
@@ -31,29 +92,18 @@ Template.featureDialog.events({
}, },
"core-select #enabledDropdown": function(event){ "core-select #enabledDropdown": function(event){
var detail = event.originalEvent.detail; var detail = event.originalEvent.detail;
if(!detail.isSelected) return; if (!detail.isSelected) return;
var value = detail.item.getAttribute("name"); var value = detail.item.getAttribute("name");
var setter; var setter;
if(value === "enabled"){ if (value === "enabled"){
setter = {enabled: true, alwaysEnabled: false}; setter = {enabled: true, alwaysEnabled: false};
} else if (value === "disabled"){ } else if (value === "disabled"){
setter = {enabled: false, alwaysEnabled: false}; setter = {enabled: false, alwaysEnabled: false};
} else{ } else {
setter = {enabled: true, alwaysEnabled: true}; setter = {enabled: true, alwaysEnabled: true};
} }
if (setter.enabled === this.enabled && setter.alwaysEnabled === this.alwaysEnabled) return; if (setter.enabled === this.enabled &&
setter.alwaysEnabled === this.alwaysEnabled) return;
Features.update(this._id, {$set: setter}); Features.update(this._id, {$set: setter});
}, },
}); });
Template.featureDialog.helpers({
feature: function(){
return Features.findOne(this.featureId);
},
usesSet: function(){
return _.isString(this.uses);
},
isEnabled: function(){
return this.enabled !== "disabled";
}
});

View File

@@ -1,7 +1,7 @@
<template name="features"> <template name="features">
<div fit> <div fit>
<div class="scroll-y" fit> <div class="scroll-y" fit>
<div class="containers"> <div class="column-container">
<!--expertiseDice--> <!--expertiseDice-->
{{>resource name="expertiseDice" title="Expertise Dice" color="teal" char=this}} {{>resource name="expertiseDice" title="Expertise Dice" color="teal" char=this}}
<!--ki--> <!--ki-->
@@ -14,27 +14,27 @@
{{>resource name="superiorityDice" title="Superiority Dice" color="teal" char=this}} {{>resource name="superiorityDice" title="Superiority Dice" color="teal" char=this}}
<!--Attacks--> <!--Attacks-->
<paper-shadow class="card container" hero-id="main" {{detailHero}}> <paper-shadow class="card">
<div class="whiteTop" hero-id="toolbar" layout horizontal center {{detailHero}}> <div class="top white">
<div flex> Attacks
<div class="containerName subhead">Attacks</div>
</div>
<!--<paper-icon-button class="black54" id="addAttackButton" icon="add"></paper-icon-button>-->
</div> </div>
<div class="containerMain listPadded"> <div class="bottom list">
{{#each attacks}} {{#each attacks}}
<div class="itemSlot"> <div class="item-slot">
<paper-item class="white attack" hero-id="main" {{detailHero}}> <div class="flexible attack item"
<div layout horizontal class="fullwidth"> hero-id="main" {{detailHero}}>
<div class="headline rightPadded" layout horizontal center> <div layout horizontal>
<div class="headline"
style="margin-right: 16px;"
layout horizontal center>
{{evaluateSigned ../_id attackBonus}} {{evaluateSigned ../_id attackBonus}}
</div> </div>
<div layout vertical flex> <div flex layout vertical>
<div class="body2"> <div class="body2">
{{name}} {{name}}
</div> </div>
<div> <div>
{{damageDice}}&nbsp;{{{evaluateSignedSpaced ../_id damageBonus}}}&nbsp;{{damageType}} {{evaluateString ../_id damage}}&nbsp;{{damageType}}
</div> </div>
{{#if details}} {{#if details}}
<div class="caption"> <div class="caption">
@@ -43,43 +43,79 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
</paper-item> </div>
</div> </div>
{{/each}} {{/each}}
</div> </div>
</paper-shadow> </paper-shadow>
<!--Proficiencies--> <!--Proficiencies-->
<paper-shadow class="card container" hero-id="main" {{detailHero "proficiencies"}}> <paper-shadow class="card">
<div id="proficiencies" <div class="white top">
class="whiteTop" Proficiencies
hero-id="toolbar" </div>
layout horizontal center <div flex class="bottom list">
{{detailHero "proficiencies"}}> {{#if weaponProfs.count}}
<div class="containerName subhead">Proficiencies</div> <div class="subhead">Weapons</div>
{{/if}}
{{#each weaponProfs}}
{{> proficiencyListItem}}
{{/each}}
{{#if armorProfs.count}}
<div class="subhead">Armor</div>
{{/if}}
{{#each armorProfs}}
{{> proficiencyListItem}}
{{/each}}
{{#if toolProfs.count}}
<div class="subhead">Tools</div>
{{/if}}
{{#each toolProfs}}
{{> proficiencyListItem}}
{{/each}}
</div> </div>
<div flex class="containerMain padded preline">{{characterProficiencies}}</div>
</paper-shadow> </paper-shadow>
<!--features--> <!--features-->
{{#each features}} {{#each features}}
<paper-shadow class="card container featureCard" hero-id="main" {{detailHero}}> <paper-shadow class="card featureCard"
<div class="containerTop {{colorClass}}" hero-id="toolbar" layout horizontal center {{detailHero}}> hero-id="main" {{detailHero}}>
<paper-ripple fit></paper-ripple> <div class="top {{colorClass}} subhead"
<div class="containerName subhead" hero-id="title" flex {{detailHero}}>{{name}}</div> layout horizontal
{{#if hasUses}}<div class="subhead" style="margin-right: 8px">{{usesLeft}}/{{usesValue}}</div>{{/if}} hero-id="toolbar" {{detailHero}}>
<paper-ripple fit></paper-ripple> <div flex hero-id="title" {{detailHero}}>
{{name}}
</div>
{{#if hasUses}}
<div style="margin-right: 8px">
{{usesLeft}}/{{usesValue}}
</div>
{{/if}}
{{#if canEnable}} {{#if canEnable}}
<core-tooltip label="Feature enabled" position="left"> <core-tooltip label="Feature enabled"
<paper-checkbox class="enabledCheckbox" checked={{enabled}}></paper-checkbox> position="left">
<paper-checkbox class="enabledCheckbox"
checked={{enabled}}
disabled={{#unless canEditCharacter charId}}true{{/unless}}>
</paper-checkbox>
</core-tooltip> </core-tooltip>
{{/if}} {{/if}}
</div> </div>
{{#if description}}<div flex class="containerMain body1 featureDescription">{{description}}</div>{{/if}} {{#if description}}
<div flex class="bottom">
{{#markdown}}{{evaluateString charId shortDescription}}{{/markdown}}
</div>
{{/if}}
{{#if hasUses}} {{#if hasUses}}
<div class="containerFoot" layout horizontal center end-justified> <div layout horizontal center end-justified>
<paper-button class="useFeature" disabled={{noUsesLeft}}>Use</paper-button> <paper-button class="useFeature"
<paper-button class="resetFeature" disabled={{usesFull}}>Reset</paper-button> disabled={{noUsesLeft}}>
Use
</paper-button>
<paper-button class="resetFeature"
disabled={{usesFull}}>
Reset
</paper-button>
</div> </div>
{{/if}} {{/if}}
</paper-shadow> </paper-shadow>
@@ -87,31 +123,43 @@
</div> </div>
<div class="fab-buffer"></div> <div class="fab-buffer"></div>
</div> </div>
<paper-fab id="addFeature" {{#if canEditCharacter _id}}
class="floatyButton" <paper-fab id="addFeature"
icon="add" class="floatyButton"
title="Add" icon="add"
role="button" title="Add"
tabindex="0" role="button"
aria-label="Add" tabindex="0"
hero-id="main"></paper-fab> aria-label="Add"
hero-id="main"></paper-fab>
{{/if}}
</div> </div>
</template> </template>
<template name="resource"> <template name="resource">
{{#if char.attributeBase name}} {{#if characterCalculate "attributeBase" char._id name}}
<paper-shadow class="card container" hero-id="main" {{detailHero}} layout horizontal> <paper-shadow class="card"
<div class="containerLeft {{getColor}}"> hero-id="main" {{detailHero name char._id}}
<div class="resourceValue">{{char.attributeValue name}}</div> layout horizontal>
<!--<div class="resourceMax">{{char.attributeBase name}}</div>--> <div class="left {{getColor}} display1 white-text"
<div class="resourceButtons"> hero-id="toolbar" {{detailHero name char._id}}
<paper-icon-button class="resourceUp" icon="arrow-drop-up" disabled={{cantIncrement}}></paper-icon-button> layout horizontal center>
<paper-icon-button class="resourceDown" icon="arrow-drop-down" disabled={{cantDecrement}}></paper-icon-button> <div style="margin-right: 8px;">
<paper-icon-button class="resourceUp"
icon="arrow-drop-up"
disabled={{cantIncrement}}>
</paper-icon-button>
<paper-icon-button class="resourceDown"
icon="arrow-drop-down"
disabled={{cantDecrement}}>
</paper-icon-button>
</div> </div>
<div>{{characterCalculate "attributeValue" char._id name}}</div>
<!--<div>/{{char.attributeBase name}}</div>-->
</div> </div>
<div class="containerRight" flex relative horizontal layout center> <div class="right clickable"
flex layout horizontal center>
{{title}} {{title}}
<paper-ripple fit></paper-ripple>
</div> </div>
</paper-shadow> </paper-shadow>
{{/if}} {{/if}}

View File

@@ -3,73 +3,84 @@ Template.features.helpers({
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}}); var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
return features; return features;
}, },
shortDescription: function() {
if (_.isString(this.description)){
return this.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0];
}
},
hasUses: function(){ hasUses: function(){
return this.usesValue() > 0; return this.usesValue() > 0;
}, },
noUsesLeft: function(){ noUsesLeft: function(){
return this.usesLeft() <= 0; return this.usesLeft() <= 0 || !canEditCharacter(this.charId);
}, },
usesFull: function(){ usesFull: function(){
return this.usesLeft() >= this.usesValue(); return this.usesLeft() >= this.usesValue() || !canEditCharacter(this.charId);
}, },
colorClass: function(){ colorClass: function(){
return getColorClass(this.color) return getColorClass(this.color);
}, },
featureOrder: function(){ featureOrder: function(){
return _.indexOf(_.keys(colorOptions), this.color); return _.indexOf(_.keys(colorOptions), this.color);
}, },
attacks: function(){ attacks: function(){
return Attacks.find({charId: this._id, enabled: true}, {sort: {color: 1, name: 1}}); return Attacks.find(
}, {charId: this._id, enabled: true},
characterProficiencies: function(){ {sort: {color: 1, name: 1}});
var char = Characters.findOne(this._id);
return char && char.proficiencies;
}, },
canEnable: function(){ canEnable: function(){
return !this.alwaysEnabled; return !this.alwaysEnabled;
} },
weaponProfs: function(){
return Proficiencies.find({charId: this._id, type: "weapon"});
},
armorProfs: function(){
return Proficiencies.find({charId: this._id, type: "armor"});
},
toolProfs: function(){
return Proficiencies.find({charId: this._id, type: "tool"});
},
}); });
Template.features.events({ Template.features.events({
"tap #addFeature": function(event){ "tap #addFeature": function(event){
var featureId = Features.insert({name: "New Feature", charId: this._id}); var featureId = Features.insert({
name: "New Feature",
charId: this._id,
enabled: true,
alwaysEnabled: true,
});
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "featureDialog", template: "featureDialog",
data: {featureId: featureId, charId: this._id}, data: {featureId: featureId, charId: this._id, startEditing: true},
heroId: featureId heroId: featureId,
}) });
}, },
"tap #addAttackButton": function(event){ "tap #addAttackButton": function(event){
var charId = this._id; var charId = this._id;
Attacks.insert({ Attacks.insert({
charId: charId charId: charId
}, function(error, id){ }, function(error, id){
if(!error){ if (!error){
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "attackDialog", template: "attackDialog",
data: {attackId: id, charId: charId}, data: {attackId: id, charId: charId},
heroId: id heroId: id,
}); });
} }
}); });
}, },
"tap .featureCard .containerTop": function(event){ "tap .featureCard .top": function(event){
var featureId = this._id; var featureId = this._id;
var charId = Template.parentData()._id; var charId = Template.parentData()._id;
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "featureDialog", template: "featureDialog",
data: {featureId: featureId, charId: charId}, data: {featureId: featureId, charId: charId},
heroId: featureId heroId: featureId,
}); });
}, },
"tap .attack": function(event){ "tap .attack": function(event){
var attackId = this._id; openParentDialog(this.parent, this.charId, this._id);
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "attackDialog",
data: {attackId: attackId, charId: charId},
heroId: attackId
});
}, },
"tap .useFeature": function(event){ "tap .useFeature": function(event){
var featureId = this._id; var featureId = this._id;
@@ -79,52 +90,60 @@ Template.features.events({
var featureId = this._id; var featureId = this._id;
Features.update(featureId, {$set: {used: 0}}); Features.update(featureId, {$set: {used: 0}});
}, },
"tap #proficiencies": function(event){
var charId = this._id;
GlobalUI.setDetail({
template: "textDialog",
data: {charId: charId, field: "proficiencies", title: "Proficiencies", color: "q"},
heroId: this._id + "proficiencies"
});
},
"tap .enabledCheckbox": function(event){ "tap .enabledCheckbox": function(event){
event.stopPropagation(); event.stopPropagation();
}, },
"change .enabledCheckbox": function(event){ "change .enabledCheckbox": function(event){
var enabled = !this.enabled; var enabled = !this.enabled;
Features.update(this._id, {$set: {enabled: enabled}}); Features.update(this._id, {$set: {enabled: enabled}});
} },
}); });
Template.resource.helpers({ Template.resource.helpers({
cantIncrement: function(){ cantIncrement: function(){
return !(this.char.attributeValue(this.name) < this.char.attributeBase(this.name)); var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
var baseBigger = value < base;
return !baseBigger || !canEditCharacter(this.char._id);
}, },
cantDecrement: function(){ cantDecrement: function(){
return !(this.char.attributeValue(this.name) > 0); var value = Characters.calculate.attributeValue(this.char._id, this.name);
var valuePositive = value > 0;
return !valuePositive || !canEditCharacter(this.char._id);
}, },
getColor: function(){ getColor: function(){
if(this.char.attributeValue(this.name) > 0){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
return this.color; return this.color;
} else { } else {
return "grey"; return "grey";
} }
} },
}); });
Template.resource.events({ Template.resource.events({
"tap .resourceUp": function(event){ "tap .resourceUp": function(event){
if(this.char.attributeValue(this.name) < this.char.attributeBase(this.name)){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
var base = Characters.calculate.attributeBase(this.char._id, this.name);
if (value < base){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = 1; modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});
} }
}, },
"tap .resourceDown": function(event){ "tap .resourceDown": function(event){
if(this.char.attributeValue(this.name) > 0){ var value = Characters.calculate.attributeValue(this.char._id, this.name);
if (value > 0){
var modifier = {$inc: {}}; var modifier = {$inc: {}};
modifier.$inc[this.name + ".adjustment"] = -1; modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false}); Characters.update(this.char._id, modifier, {validate: false});
} }
} },
"tap .right": function(event, instance) {
GlobalUI.setDetail({
template: "attributeDialog",
data: {name: this.title, statName: this.name, charId: this.char._id},
heroId: this.char._id + this.name,
});
},
}); });

View File

@@ -0,0 +1,22 @@
<template name="carryCapacityBar">
<div class="carryCapacityBar">
<div class="carriedWeightBar"
style="width: {{carriedPercent}}%;
background-color: {{carriedColor}}">
</div>
<div class="tick"
style="width: 33.333%;">
</div>
<div class="tick"
style="width: 66.666%;">
</div>
</div>
{{#if overCarriedPercent}}
<div class="carryCapacityBar">
<div class="carriedWeightBar"
style="width: {{overCarriedPercent}}%;
background-color: {{overCarriedColor}}">
</div>
</div>
{{/if}}
</template>

View File

@@ -0,0 +1,65 @@
var getFractionCarried = function(char) {
//find out the weight
var weight = 0;
Containers.find(
{charId: char._id, isCarried: true}
).forEach(function(container){
weight += container.totalWeight();
});
Items.find(
{charId: char._id, "parent.id": char._id},
{fields: {weight : 1, quantity: 1}}
).forEach(function(item){
weight += item.totalWeight();
});
//get strength
var strength = Characters.calculate.attributeValue(char._id, "strength");
var capacity = strength * 15;
return weight / capacity;
};
Template.carryCapacityBar.onCreated(function() {
var self = this;
self.carriedFraction = new ReactiveVar(0);
self.autorun(function() {
self.carriedFraction.set(getFractionCarried(self.data));
});
});
Template.carryCapacityBar.helpers({
carriedPercent: function() {
var percent = 100 * Template.instance().carriedFraction.get();
return percent > 100 ? 100 : percent;
},
overCarriedPercent: function() {
var percent = 100 * Template.instance().carriedFraction.get();
var overPercent = percent - 100;
if (overPercent < 0) return 0;
if (overPercent > 100) return 100;
return overPercent;
},
carriedColor: function() {
var frac = Template.instance().carriedFraction.get();
if (frac < 1 / 3){
return "#2196F3";
} else if (frac < 2 / 3){
return "#CDDC39";
} else if (frac < 1) {
return "#FFC107";
} else {
return "#F44336";
}
},
overCarriedColor: function() {
var frac = Template.instance().carriedFraction.get();
if (frac < 1 / 3){
return "#2196F3";
} else if (frac < 2 / 3){
return "#CDDC39";
} else if (frac < 1) {
return "#FFC107";
} else {
return "#F44336";
}
},
});

View File

@@ -0,0 +1,14 @@
.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

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

View File

@@ -0,0 +1,20 @@
Template.carryDialog.helpers({
carriedWeight: function() {
var weight = 0;
Containers.find(
{charId: this.charId, isCarried: true}
).forEach(function(container){
weight += container.totalWeight();
});
Items.find(
{charId: this.charId, "parent.id": this.charId},
{fields: {weight : 1, quantity: 1}}
).forEach(function(item){
weight += item.totalWeight();
});
return weight;
},
color: function() {
if (this.color) return this.color + " white-text";
},
});

View File

@@ -1,22 +1,46 @@
<template name="containerDialog"> <template name="containerDialog">
{{#with container}} {{#with container}}
{{#baseDialog title=name class=colorClass}} {{#baseDialog title=name class=colorClass startEditing=../startEditing}}
<!--Name and plural name--> {{> containerView}}
<paper-input id="containerNameInput" label="Name" floatinglabel value={{name}}></paper-input> {{else}}
<!--Weight--> {{> containerEdit}}
<paper-input-decorator label="Weight" floatinglabel>
<input id="weightInput" type="number" value={{weight}}>
</paper-input-decorator>
<!--Value-->
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
<!--Description-->
<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>
{{/baseDialog}} {{/baseDialog}}
{{/with}} {{/with}}
</template> </template>
<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>
<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>
</template>
<template name="containerView">
<div 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>
</table>
</div>
{{#if description}}
<hr class="vertMargin">
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
</template>

View File

@@ -10,11 +10,21 @@ Template.containerDialog.events({
}, },
"tap #deleteButton": function(event, instance){ "tap #deleteButton": function(event, instance){
Containers.softRemoveNode(instance.data.containerId); Containers.softRemoveNode(instance.data.containerId);
GlobalUI.deletedToast(instance.data.containerId, "Containers", "Container and contents"); GlobalUI.deletedToast(
instance.data.containerId,
"Containers", "Container and contents"
);
GlobalUI.closeDetail(); GlobalUI.closeDetail();
}, },
});
Template.containerEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.containerEdit.events({
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors //TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #containerNameInput, input #containerNameInput": function(event){ "change #containerNameInput": function(event){
var name = Template.instance().find("#containerNameInput").value; var name = Template.instance().find("#containerNameInput").value;
Containers.update(this._id, {$set: {name: name}}); Containers.update(this._id, {$set: {name: name}});
}, },
@@ -26,8 +36,8 @@ Template.containerDialog.events({
var value = +Template.instance().find("#valueInput").value; var value = +Template.instance().find("#valueInput").value;
Containers.update(this._id, {$set: {value: value}}); Containers.update(this._id, {$set: {value: value}});
}, },
"change #containerDescriptionInput": function(event){ "change #containerDescriptionInput": function(event, instance){
var description = Template.instance().find("#containerDescriptionInput").value; var description = instance.find("#containerDescriptionInput").value;
Containers.update(this._id, {$set: {description: description}}); Containers.update(this._id, {$set: {description: description}});
} },
}); });

View File

@@ -1,153 +0,0 @@
div#stats {
-webkit-column-width: 200px;
-moz-column-width: 200px;
column-width: 200px;
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
}
.containers {
-webkit-column-width: 300px;
-moz-column-width: 300px;
column-width: 300px;
-webkit-column-gap: 8px;
-moz-column-gap: 8px;
column-gap: 8px;
-moz-column-fill: balance;
column-fill: balance;
padding: 8px;
}
.containerLeft {
padding: 16px 16px 16px 24px;
display: flex;
justify-content: center;
flex-direction: row;
border-radius: 2px 0 0 2px;
/* same style as display-1 */
font-size: 34px;
font-weight: 400;
color: #ffffff;
color: rgba(255,255,255,0.54);
letter-spacing: 0;
}
.statCard .containerLeft {
padding: 16px;
}
.containerRight {
padding: 16px;
cursor: pointer;
/* same style as subhead */
font-size: 16px;
font-weight: 400;
margin: 0;
color: #000;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010em;
}
.resourceValue {
display: inline-block;
}
.resourceMax {
display: inline-block;
align-self: flex-end;
/* same style as subhead */
font-size: 16px;
font-weight: 400;
margin: 0;
color: #fff;
color: rgba(255,255,255,0.54);
letter-spacing: 0.010em;
}
.resourceMax:before {
content: "/";
}
.resourceButtons {
margin: -16px -16px -16px 8px;
align-self: center;
}
.resourceButtons paper-icon-button{
width: 32px;
height: 32px;
padding: 0;
display: block;
}
.resourceButtons paper-icon-button[disabled]{
color: rgba(255, 255, 255, 0.26);
}
.resourceButtons /deep/ core-icon {
width: 32px;
height: 32px;
}
.containerTop {
cursor: pointer;
padding: 16px;
position: relative;
border-radius: 2px 2px 0 0;
}
.equipmentTop {
padding: 16px;
border-bottom: rgba(0,0,0,0.12) solid 1px;
}
.containerMain {
padding: 16px;
}
.equipmentMain {
padding-bottom: 16px;
}
.inventoryItem {
background: white;
transition: box-shadow 0.3s ease,
opacity 0.5s ease-in-out;
height: 40px;
margin: 1px 0 1px 0;
font-size: 16px;
color: rgba(0,0,0,0.87);
letter-spacing: 0.010em;
}
.inventoryItem core-icon, .inventoryItem paper-icon-button {
color: rgba(0,0,0,0.54);
}
.inventoryItem core-icon {
margin-right: 16px;
}
.inventoryItem /deep/ .button-content {
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.inventoryItem[hero] {
box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.37);
}
.itemSlot {
background-color: rgb(232, 232, 232);
background-color: rgba(0, 0, 0, 0.1);
}
#inventory .containerMain {
padding: 0 0 16px 0;
}

View File

@@ -1,73 +1,124 @@
<template name="inventory"> <template name="inventory">
<div fit> <div fit>
<div id="inventory" class="scroll-y" fit> <div id="inventory" class="scroll-y" fit>
<div class="containers"> <div class="column-container">
<!--Net Worth--> <!--Net Worth-->
<paper-shadow class="card container" hero-id="main" {{detailHero}} layout horizontal> <paper-shadow class="card">
<div class="indigo white-text subhead padded leftRound" layout horizontal center> <div class="white top" layout horizontal center>
Net Worth <div class="subhead" flex>
</div> Net Worth
<div class="padded" layout horizontal center> </div>
{{valueString netWorth}} <div>
{{valueString netWorth}}
</div>
</div> </div>
</paper-shadow> </paper-shadow>
<!--Weight Carried--> <!--Weight Carried-->
<paper-shadow class="card container" hero-id="main" {{detailHero}} layout horizontal> <paper-shadow class="card"
<div class="green white-text subhead padded leftRound" layout horizontal center> hero-id="main" {{detailHero "weightCarried" _id}}>
Weight Carried <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>
<div class="padded" layout horizontal center> <div class="bottom green" style="padding: 0;">
{{round weightCarried}}lbs {{> carryCapacityBar}}
</div> </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> </paper-shadow>
<!--Equipment--> <!--Equipment-->
<paper-shadow class="card container equipmentContainer"> <paper-shadow class="card equipmentContainer">
<div class="equipmentTop" layout horizontal center> <div class="white top" layout horizontal center>
<div class="containerName subhead" flex> <div class="subhead" flex>
Equipment Equipment
</div> </div>
<div class="caption" style="margin-right: 8px">{{valueString equipmentValue}}</div> <div class="caption" style="margin-right: 8px">
<div class="caption">{{round equipmentWeight}}lbs</div> {{valueString equipmentValue}}
</div>
<div class="caption">
{{round equipmentWeight}}lbs
</div>
</div> </div>
<div flex class="equipmentMain"> <div flex class="bottom list">
{{#if attuned.count}} {{#if attuned.count}}
<div class="list-subhead" layout horizontal center>Attuned</div> <div class="subhead">Attuned</div>
{{/if}} {{/if}}
{{#each attuned}} {{#each attuned}}
{{>inventoryItem}} {{>inventoryItem}}
{{/each}} {{/each}}
{{#if attuned.count}}<div class="list-subhead" layout horizontal center>Equipment</div>{{/if}} {{#if attuned.count}}
<div class="subhead">Equipment</div>
{{/if}}
{{#each equipment}} {{#each equipment}}
{{>inventoryItem}} {{>inventoryItem}}
{{/each}} {{/each}}
</div> </div>
</paper-shadow> </paper-shadow>
<!--Carried Items--> <!--Carried Items-->
<paper-shadow class="card container carriedContainer"> <paper-shadow class="card carriedContainer">
<div class="equipmentTop" layout horizontal center> <div class="white top" layout horizontal center>
<div class="containerName subhead" flex> <div class="subhead" flex>
Carried Carried
</div> </div>
<div class="caption" style="margin-right: 8px">{{valueString carriedValue}}</div> <div class="caption" style="margin-right: 8px">
<div class="caption">{{round carriedWeight}}lbs</div> {{valueString carriedValue}}
</div>
<div class="caption">
{{round carriedWeight}}lbs
</div>
</div> </div>
<div flex class="containerMain"> <div flex class="bottom list">
{{#each carriedItems}} {{#each carriedItems}}
{{>inventoryItem}} {{>inventoryItem}}
{{/each}} {{/each}}
</div> </div>
</paper-shadow> </paper-shadow>
{{#each containers}} {{#each containers}}
<paper-shadow class="card container itemContainer" hero-id="main" {{detailHero}} style="order: {{containerOrder}};"> <paper-shadow class="card itemContainer"
<div class="containerTop {{colorClass}}" hero-id="toolbar" layout horizontal center {{detailHero}}> hero-id="main" {{detailHero}}>
<div class="containerName subhead" hero-id="title" flex {{detailHero}}>{{name}}</div> <div class="top {{colorClass}}"
<div class="caption" style="margin-right: 8px">{{valueString totalValue}}</div> hero-id="toolbar" {{detailHero}}
<div class="caption" style="margin-right: 8px">{{round totalWeight}}lbs</div> layout horizontal center>
<div class="subhead" flex
hero-id="title" {{detailHero}}>
{{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"> <core-tooltip label="Container carried" position="left">
<paper-checkbox class="carriedCheckbox" checked={{isCarried}}></paper-checkbox> <paper-checkbox class="carriedCheckbox"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
checked={{isCarried}}>
</paper-checkbox>
</core-tooltip> </core-tooltip>
</div> </div>
<div flex class="containerMain"> <div class="bottom list">
{{#each items ../_id _id}} {{#each items ../_id _id}}
{{>inventoryItem}} {{>inventoryItem}}
{{/each}} {{/each}}
@@ -77,17 +128,45 @@
</div> </div>
<div class="fab-buffer"></div> <div class="fab-buffer"></div>
</div> </div>
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3"> {{#if canEditCharacter _id}}
<paper-fab-menu-item id="addItem" icon="note-add" color="#d23f31" tooltip="Item"></paper-fab-menu-item> {{#fabMenu}}
<paper-fab-menu-item id="addContainer" icon="work" color="#d23f31" tooltip="Container"></paper-fab-menu-item> <core-tooltip label="New container" position="left">
</paper-fab-menu> <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> </div>
</template> </template>
<template name="inventoryItem"> <template name="inventoryItem">
<div class="itemSlot"> <div class="item-slot">
<paper-item class="inventoryItem {{hidden}}" hero-id="main" noink {{detailHero}} layout horizontal draggable="true"> <div class="item {{hidden}} inventoryItem"
{{#if ne1 quantity}}{{quantity}}&nbsp;{{/if}}{{pluralName}} hero-id="main" {{detailHero}}
</paper-item> layout horizontal center
draggable={{canEditCharacter charId}}>
<div flex class="itemName">
{{#if ne1 quantity}}{{quantity}}&nbsp;{{/if}}{{pluralName}}
</div>
{{#if settings.showIncrement}}{{#if canEditCharacter charId}}
<div class="incrementButtons">
<paper-icon-button class="addItemQuantity"
icon="add"
style="margin-right: -8px"></paper-icon-button>
<paper-icon-button class="subItemQuantity"
disabled={{lt1 quantity}}
icon="remove"
style="margin-right: -8px"></paper-icon-button>
</div>
{{/if}}{{/if}}
</div>
</div> </div>
</template> </template>

View File

@@ -7,16 +7,28 @@ Template.inventory.helpers({
return Containers.find({charId: this._id}, {sort: {color: 1, name: 1}}); return Containers.find({charId: this._id}, {sort: {color: 1, name: 1}});
}, },
items: function(charId, containerId){ items: function(charId, containerId){
return Items.find({charId: charId, "parent.id": containerId }, {sort: {color: 1, name: 1}}); return Items.find(
{charId: charId, "parent.id": containerId},
{sort: {color: 1, name: 1}}
);
}, },
attuned: function(){ attuned: function(){
return Items.find({ charId: this._id, enabled: true, requiresAttunement: true }, {sort: {color: 1, name: 1}}); return Items.find(
{charId: this._id, enabled: true, requiresAttunement: true},
{sort: {color: 1, name: 1}}
);
}, },
equipment: function(){ equipment: function(){
return Items.find({ charId: this._id, enabled: true, requiresAttunement: false }, {sort: {color: 1, name: 1}}); return Items.find(
{charId: this._id, enabled: true, requiresAttunement: false},
{sort: {color: 1, name: 1}}
);
}, },
carriedItems: function(){ carriedItems: function(){
return Items.find({charId: this._id, enabled: false, "parent.id": this._id}, {sort: {color: 1, name: 1}}); return Items.find(
{charId: this._id, enabled: false, "parent.id": this._id},
{sort: {color: 1, name: 1}}
);
}, },
showAddButtons: function(){ showAddButtons: function(){
return Template.instance().showAddButtons.get(); return Template.instance().showAddButtons.get();
@@ -26,45 +38,82 @@ Template.inventory.helpers({
}, },
netWorth: function(){ netWorth: function(){
var worth = 0; var worth = 0;
Items.find({charId: this._id}, {fields: {value : 1, quantity: 1}}).forEach(function(item){ Items.find(
{charId: this._id},
{fields: {value : 1, quantity: 1}}
).forEach(function(item){
worth += item.totalValue(); worth += item.totalValue();
}); });
Containers.find(
{charId: this._id},
{fields: {value : 1}}
).forEach(function(container) {
if (container.value) worth += container.value;
});
return worth; return worth;
}, },
weightCarried: function(){ weightCarried: function(){
var weight = 0; var weight = 0;
Containers.find({charId: this._id, isCarried: true}).forEach(function(container){ Containers.find(
{charId: this._id, isCarried: true}
).forEach(function(container){
weight += container.totalWeight(); weight += container.totalWeight();
}); });
Items.find({charId: this._id, "parent.id": this._id}, {fields: {weight : 1, quantity: 1}}).forEach(function(item){ Items.find(
{charId: this._id, "parent.id": this._id},
{fields: {weight : 1, quantity: 1}}
).forEach(function(item){
weight += item.totalWeight(); weight += item.totalWeight();
}); });
return weight; return weight;
}, },
encumberedBuffs: function(){
return Buffs.find({
charId: this._id,
type: "inate",
name: {$in: [
"Encumbered",
"Heavily encumbered",
"Over encumbered",
"Can't move load",
]},
});
},
equipmentValue: function(){ equipmentValue: function(){
var value = 0; var value = 0;
Items.find({charId: this._id, enabled: true}, {fields: {value : 1, quantity: 1}}).forEach(function(item){ Items.find(
{charId: this._id, enabled: true},
{fields: {value : 1, quantity: 1}}
).forEach(function(item){
value += item.totalValue(); value += item.totalValue();
}); });
return value; return value;
}, },
equipmentWeight: function(){ equipmentWeight: function(){
var weight = 0; 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(); weight += item.totalWeight();
}); });
return weight; return weight;
}, },
carriedValue: function(){ carriedValue: function(){
var value = 0; var value = 0;
Items.find({charId: this._id, enabled: false, "parent.id": this._id}, {fields: {value : 1, quantity: 1}}).forEach(function(item){ Items.find(
{charId: this._id, enabled: false, "parent.id": this._id},
{fields: {value : 1, quantity: 1}}
).forEach(function(item){
value += item.totalValue(); value += item.totalValue();
}); });
return value; return value;
}, },
carriedWeight: function(){ carriedWeight: function(){
var weight = 0; var weight = 0;
Items.find({charId: this._id, enabled: false, "parent.id": this._id}, {fields: {weight : 1, quantity: 1}}).forEach(function(item){ Items.find(
{charId: this._id, enabled: false, "parent.id": this._id},
{fields: {weight : 1, quantity: 1}}
).forEach(function(item){
weight += item.totalWeight(); weight += item.totalWeight();
}); });
return weight; return weight;
@@ -72,29 +121,54 @@ Template.inventory.helpers({
}); });
Template.inventory.events({ Template.inventory.events({
"tap #addItem": function(event){ "tap .addItem": function(event){
var charId = this._id; var charId = this._id;
Items.insert({ Items.insert({
charId: charId, charId: charId,
parent:{ parent:{
id: charId, id: charId,
collection: "Characters" collection: "Characters",
} },
}, function(err, itemId){ }, function(err, itemId){
if(err) throw err; if (err) throw err;
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "itemDialog", template: "itemDialog",
data: {itemId: itemId, charId: charId}, data: {itemId: itemId, charId: charId, startEditing: true},
heroId: itemId heroId: itemId,
}); });
}); });
}, },
"tap #addContainer": function(event){ "tap .addContainer": function(event){
var containerId = Containers.insert({name: "New Container", isCarried: true, charId: this._id}); var containerId = Containers.insert({
name: "New Container",
isCarried: true,
charId: this._id,
});
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "containerDialog", template: "containerDialog",
data: {containerId: containerId, charId: this.charId}, data: {
heroId: containerId containerId: containerId,
charId: this.charId,
startEditing: true,
},
heroId: containerId,
});
},
"tap .weightCarried": function(event) {
var charId = this._id;
GlobalUI.setDetail({
template: "carryDialog",
data: {charId: charId, color: "green"},
heroId: charId + "weightCarried",
});
},
"tap .buff": function(event){
var buffId = this._id;
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "buffDialog",
data: {buffId: buffId, charId: charId},
heroId: buffId,
}); });
}, },
"tap .inventoryItem": function(event){ "tap .inventoryItem": function(event){
@@ -103,14 +177,39 @@ Template.inventory.events({
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "itemDialog", template: "itemDialog",
data: {itemId: itemId, charId: charId}, data: {itemId: itemId, charId: charId},
heroId: itemId heroId: itemId,
}); });
}, },
"tap .containerTop": function(event){ "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) {
event.stopPropagation();
},
"tap .addItemQuantity": function(event) {
var itemId = this._id;
Items.update(itemId, {$set: {quantity: this.quantity + 1}});
},
"tap .subItemQuantity": function(event) {
var itemId = this._id;
Items.update(itemId, {$set: {quantity: this.quantity - 1}});
},
"tap .itemContainer .top": function(event){
GlobalUI.setDetail({ GlobalUI.setDetail({
template: "containerDialog", template: "containerDialog",
data: {containerId: this._id, charId: this.charId}, data: {containerId: this._id, charId: this.charId},
heroId: this._id heroId: this._id,
}); });
}, },
"tap .carriedCheckbox": function(event){ "tap .carriedCheckbox": function(event){
@@ -118,88 +217,113 @@ Template.inventory.events({
}, },
"change .carriedCheckbox": function(event){ "change .carriedCheckbox": function(event){
var carried; var carried;
if(this.isCarried) carried = false; if (this.isCarried) carried = false;
else carried = true; else carried = true;
Containers.update(this._id, {$set: {isCarried: carried}}); Containers.update(this._id, {$set: {isCarried: carried}});
} },
}); });
Template.inventoryItem.helpers({ Template.inventoryItem.helpers({
ne1: function(num){ ne1: function(num){
return num !== 1; return num !== 1;
}, },
lt1: function(num) {
return num < 1;
},
hidden: function(){ hidden: function(){
return Session.equals("inventory.dragItemId", this._id)? "hidden" : null; return Session.equals("inventory.dragItemId", this._id) ? "hidden" : null;
} },
}); });
Template.layout.events({ Template.layout.events({
"dragstart .inventoryItem": function(event, instance){ "dragstart .inventoryItem": function(event, instance){
event.originalEvent.dataTransfer.setData("dicecloud-id/items", this._id);
Session.set("inventory.dragItemId", this._id); Session.set("inventory.dragItemId", this._id);
Session.set("inventory.dragItemOriginalContainer", this.container); },
Session.set("inventory.dragItemOriginalCharacter", this.charId); "dragover .itemContainer, dragenter .itemContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .equipmentContainer, dragenter .equipmentContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .carriedContainer, dragenter .carriedContainer":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
},
"dragover .characterRepresentative, dragenter .characterRepresentative":
function(event, instance){
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
event.preventDefault();
}
}, },
"dragend .inventoryItem": function(event, instance){ "dragend .inventoryItem": function(event, instance){
resetInvetorySession(); //this is a valid drop zone Session.set("inventory.dragItemId", null);
}, },
"dragover .itemContainer": function(event, instance){ "drop .itemContainer": function(event, instance){
event.preventDefault(); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
}, if (event.ctrlKey){
"dragover .equipmentContainer": function(event, instance){
event.preventDefault();
},
"dragover .carriedContainer": function(event, instance){
event.preventDefault();
},
"drop .itemContainer": function(event, instacne){
var item = Items.findOne(Session.get("inventory.dragItemId"));
if(event.ctrlKey){
//split the stack to the container //split the stack to the container
GlobalUI.showDialog({ GlobalUI.showDialog({
template: "splitStackDialog", template: "splitStackDialog",
data: { data: {
id: item._id, id: itemId,
parentCollection: "Containers", parentCollection: "Containers",
parentId: this._id parentId: this._id,
} },
}); });
} else{ } else {
//move item to the container //move item to the container
item.moveToContainer(this._id); Meteor.call("moveItemToContainer", itemId, this._id);
} }
resetInvetorySession(); Session.set("inventory.dragItemId", null);
}, },
"drop .equipmentContainer": function(event, instance){ "drop .equipmentContainer": function(event, instance){
var charId = Session.get("inventory.dragItemOriginalCharacter"); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
var item = Items.findOne(Session.get("inventory.dragItemId")); Meteor.call("equipItem", itemId, this._id);
item.equip(charId); Session.set("inventory.dragItemId", null);
resetInvetorySession();
}, },
"drop .carriedContainer": function(event, instance){ "drop .carriedContainer": function(event, instance){
var charId = Session.get("inventory.dragItemOriginalCharacter"); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
var item = Items.findOne(Session.get("inventory.dragItemId")); if (event.ctrlKey){
if(event.ctrlKey){
//split the stack to the container //split the stack to the container
GlobalUI.showDialog({ GlobalUI.showDialog({
template: "splitStackDialog", template: "splitStackDialog",
data: { data: {
id: item._id, id: itemId,
parentCollection: "Characters", parentCollection: "Characters",
parentId: this._id parentId: this._id,
} },
}); });
} else{ } else {
//move item to the character //move item to the character
item.moveToCharacter(this._id); Meteor.call("moveItemToCharacter", itemId, this._id);
} }
resetInvetorySession();
}
});
var resetInvetorySession = function(){
_.defer(function(){
Session.set("inventory.dragItemId", null); Session.set("inventory.dragItemId", null);
Session.set("inventory.dragItemOriginalContainer", null); },
Session.set("inventory.dragItemOriginalCharacter", null); "drop .characterRepresentative": function(event, instance) {
}); var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
}; if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
template: "splitStackDialog",
data: {
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
//move item to the character
Meteor.call("moveItemToCharacter", itemId, this._id);
}
Session.set("inventory.dragItemId", null);
},
});

View File

@@ -1,57 +1,113 @@
<template name="itemDialog"> <template name="itemDialog">
{{#with item}} {{#with item}}
{{#baseDialog title=name class=colorClass}} {{#baseDialog title=itemHeading class=colorClass startEditing=../startEditing}}
<!--Name and plural name--> {{> itemDetails}}
<paper-input id="itemNameInput" class="fullwidth" label="Name" floatinglabel value={{name}}></paper-input> {{else}}
{{# if ne1 quantity}}<paper-input id="itemPluralInput" label="Plural Name" floatinglabel value={{plural}}></paper-input>{{/if}} {{> itemEdit}}
<!--Container dropdown-->
<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>
<!--Equipped-->
<div center horizontal layout>
<div flex>Equipped</div>
<paper-toggle-button id="equippedInput"
checked={{enabled}}
role="button"
aria-pressed="false"
tabindex="0"
touch-action="pan-y">
</paper-toggle-button>
</div>
<!--Equipped-->
<div center horizontal layout>
<div flex>Requires Attunement</div>
<paper-checkbox id="attunementCheckbox" checked={{requiresAttunement}}></paper-checkbox>
</div>
<!--Quantity-->
<paper-input-decorator label="Quantity" floatinglabel>
<input id="quantityInput" type="number" value={{quantity}}>
</paper-input-decorator>
<!--Weight-->
<paper-input-decorator label="Weight Each (lbs)" floatinglabel>
<input id="weightInput" type="number" value={{weight}}>
</paper-input-decorator>
<!--Value-->
<paper-input-decorator label="Value Each (GP)" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
<!--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>
<!--Effects-->
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId type="equipment" enabled=equipped name=name}}
<!--Attacks-->
{{> attackEditList parentId=_id parentCollection="Items" charId=charId type="equipment" enabled=equipped name=name}}
{{/baseDialog}} {{/baseDialog}}
{{/with}} {{/with}}
</template> </template>
<template name="itemDetails">
<div layout horizontal wrap center justified class="headline">
{{#if weight}}<div class="sideMargin">{{round totalWeight}}lbs</div>{{/if}}
{{#if value}}<div>{{valueString totalValue}}</div>{{/if}}
</div>
<div layout horizontal wrap class="caption">
{{#if enabled}}<div class="vertMargin" style="margin-right: 16px">Equipped</div>{{/if}}
{{#if requiresAttunement}}<div class="vertMargin">Requires Attunement</div>{{/if}}
</div>
{{#if description}}
<hr style="margin: 16px 0 16px 0;">
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}
</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>
{{# if ne1 quantity}}
<paper-input flex id="itemPluralInput"
label="Plural Name"
floatinglabel
value={{plural}}></paper-input>
{{/if}}
</div>
<div center horizontal layout>
<div class="padded">Show increment buttons</div>
<paper-checkbox id="incrementCheckbox"
checked={{settings.showIncrement}}>
</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>
<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>
<!--Value-->
<paper-input-decorator label="Value Each (GP)" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
</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>
<!--Effects-->
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
<!--Attacks-->
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
</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>
</template>

View File

@@ -1,20 +1,38 @@
var getContainers = function(charId){ var getContainers = function(charId){
return Containers.find({charId: charId}, {sort: {name: 1, _id: 1}, fields: {name: 1}}).fetch(); return Containers.find(
{charId: charId},
{sort: {name: 1, _id: 1}, fields: {name: 1}}
);
}; };
Template.itemDialog.onCreated(function(){
this.editing = new ReactiveVar(!!this.data.startEditing);
});
Template.itemDialog.helpers({ Template.itemDialog.helpers({
item: function(){ item: function(){
return Items.findOne(this.itemId); return Items.findOne(this.itemId);
}, },
containers: function(){ editing: function(){
return getContainers(this.charId); return Template.instance().editing.get();
},
itemHeading: function(){
if (this.quantity === 1){
return this.name;
} else {
var pName = this.plural || this.name;
return this.quantity + " " + pName;
}
}, },
ne1: function(num){
return num != 1;
}
}); });
Template.itemDialog.events({ Template.itemDialog.events({
"tap #editButton": function(event, instance){
instance.editing.set(true);
},
"tap #doneEditingButton": function(event, instance){
instance.editing.set(false);
},
"color-change": function(event, instance){ "color-change": function(event, instance){
Items.update(instance.data.itemId, {$set: {color: event.color}}); Items.update(instance.data.itemId, {$set: {color: event.color}});
}, },
@@ -23,6 +41,19 @@ Template.itemDialog.events({
GlobalUI.deletedToast(instance.data.itemId, "Items", "Item"); GlobalUI.deletedToast(instance.data.itemId, "Items", "Item");
GlobalUI.closeDetail(); GlobalUI.closeDetail();
}, },
});
Template.itemEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.itemEdit.helpers({
ne1: function(num){
return num != 1;
},
});
Template.itemEdit.events({
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors //TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #itemNameInput": function(event){ "change #itemNameInput": function(event){
var name = Template.instance().find("#itemNameInput").value; var name = Template.instance().find("#itemNameInput").value;
@@ -50,24 +81,33 @@ Template.itemDialog.events({
}, },
"change #equippedInput": function(event){ "change #equippedInput": function(event){
var equipped = Template.instance().find("#equippedInput").checked; var equipped = Template.instance().find("#equippedInput").checked;
var item = Items.findOne(this._id); if (equipped){
if(item){ Meteor.call("equipItem", this._id, this.charId);
if(equipped){ } else {
item.equip(); Meteor.call("unequipItem", this._id, this.charId);
} else {
item.unequip();
}
} }
}, },
"change #incrementCheckbox": function(event){
var value = event.currentTarget.checked;
Items.update(this._id, {$set: {"settings.showIncrement": value}});
},
"change #attunementCheckbox": function(event){ "change #attunementCheckbox": function(event){
var value = event.currentTarget.checked; var value = event.currentTarget.checked;
Items.update(this._id, {$set: {requiresAttunement: value}}); Items.update(this._id, {$set: {requiresAttunement: value}});
}, },
"core-select #containerDropDown": function(event){ });
var detail = event.originalEvent.detail;
if(!detail.isSelected) return; Template.containerDropdown.helpers({
var containerId = detail.item.getAttribute("name"); containers: function(){
var item = Items.findOne(Template.currentData().itemId); return getContainers(this.charId);
item.moveToContainer(containerId); }
});
Template.containerDropdown.events({
"core-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);
} }
}); });

View File

@@ -0,0 +1,7 @@
html /deep/ .moveItemDialog paper-tabs::shadow #selectionBar {
background-color: #D50000;
}
html /deep/ .moveItemDialog paper-tab::shadow #ink {
color: #D50000;
}

View File

@@ -0,0 +1,49 @@
<template name="moveItemDialog">
<div class="moveItemDialog">
<paper-tabs selected="{{selectedTab}}">
<paper-tab name="containers"
class="clickable">
Containers
</paper-tab>
<paper-tab name="characters"
class="clickable">
Characters
</paper-tab>
</paper-tabs>
<core-animated-pages selected="{{selectedTab}}"
transitions="slide-from-right"
style="width: 250px;
height: 200px;
overflow-y: auto;
overflow-x: hidden;">
<section name="containers">
<core-menu id="containerMenu" style="margin: 0;">
{{#each containers}}
<paper-item name={{_id}}
layout horizontal center>
<core-icon icon="image:brightness-1"
style="color: {{hexColor color}};
margin-right: 16px;">
</core-icon>
<div>{{name}}</div>
</paper-item>
{{/each}}
</core-menu>
</section>
<section name="characters">
<core-menu id="characterMenu" style="margin: 0;">
{{#each characters}}
<paper-item name={{_id}}
layout horizontal center>
<div class="item small">
{{name}}
</div>
</paper-item>
{{/each}}
</core-menu>
</section>
</core-animated-pages>
</div>
<paper-button id="cancelButton" affirmative> Cancel </paper-button>
<paper-button id="moveButton" affirmative> Move </paper-button>
</template>

View File

@@ -0,0 +1,56 @@
Template.moveItemDialog.onCreated(function() {
Session.setDefault("moveItemDialogTab", "containers");
});
Template.moveItemDialog.helpers({
selectedTab: function() {
return Session.get("moveItemDialogTab");
},
characters: function() {
var userId = Meteor.userId();
return Characters.find(
{
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
],
_id: {$ne: this.charId},
},
{fields: {name: 1}}
);
},
containers: function(){
return Containers.find(
{
charId: this.charId,
_id: {$ne: this.containerId},
},
{
fields: {color: 1, name: 1},
sort: {color: 1, name: 1},
}
);
},
});
Template.moveItemDialog.events({
"tap paper-tab": function(event) {
Session.set("moveItemDialogTab", event.currentTarget.getAttribute("name"));
},
"tap #moveButton": function(event, instance) {
var tab = Session.get("moveItemDialogTab");
if (tab === "containers"){
var containerId = instance.find("#containerMenu").selected;
if (!containerId) throw "no menu selection";
Meteor.call("moveItemToContainer", this.itemId, containerId);
} else if (tab === "characters"){
var characterId = instance.find("#characterMenu").selected;
if (!characterId) throw "no menu selection";
Meteor.call("moveItemToCharacter", this.itemId, characterId);
} else {
throw "Move item dialog tab is not set to containers or character," +
" it is set to " + tab;
}
},
});

View File

@@ -1,27 +1,27 @@
Template.splitStackDialog.helpers({ Template.splitStackDialog.helpers({
quantity: function(){ quantity: function(){
var item = Items.findOne(this.id); var item = Items.findOne(this.id);
if(item) return Math.round(item.quantity/2); if (item) return Math.round(item.quantity / 2);
} }
}); });
Template.splitStackDialog.events({ Template.splitStackDialog.events({
'tap #moveButton': function(event, instance){ "tap #moveButton": function(event, instance){
var item = Items.findOne(this.id); Meteor.call(
if(item){ "splitItemToParent",
item.splitToParent( this.id,
{collection: this.parentCollection , id: this.parentId}, +instance.find("#quantityInput").value,
+instance.find('#quantityInput').value {collection: this.parentCollection , id: this.parentId}
); );
}
}, },
'tap #oneButton':function(event, instance){ "tap #oneButton":function(event, instance){
instance.find('#quantityInput').value = 1; instance.find("#quantityInput").value = 1;
}, },
'tap #halfButton':function(event, instance){ "tap #halfButton":function(event, instance){
instance.find('#quantityInput').value = Math.round(Items.findOne(this.id).quantity/2); var val = Math.round(Items.findOne(this.id).quantity / 2);
instance.find("#quantityInput").value = val;
},
"tap #allButton":function(event, instance){
instance.find("#quantityInput").value = Items.findOne(this.id).quantity;
}, },
'tap #allButton':function(event, instance){
instance.find('#quantityInput').value = Items.findOne(this.id).quantity;
}
}); });

View File

@@ -1,12 +1,25 @@
<template name="classDialog"> <template name="classDialog">
{{#with class}} {{#with class}}
{{#baseDialog title=name class=colorClass hideColor="true"}} {{#baseDialog title=name class=colorClass startEditing=../startEditing}}
<div layout vertical center>
<div class="display2">
{{level}}
</div>
<div>
level
</div>
</div>
{{> effectsViewList charId=charId parentId=_id}}
{{> proficiencyViewList charId=charId parentId=_id}}
{{else}}
<!--Name--> <!--Name-->
<paper-input id="classNameInput" label="Class Name" floatinglabel value={{name}}></paper-input> <paper-input id="classNameInput" label="Class Name" floatinglabel value={{name}}></paper-input>
<!--Level--> <!--Level-->
<paper-input id="levelValueInput" label="Level" floatinglabel value={{level}}></paper-input> <paper-input id="levelValueInput" label="Level" floatinglabel value={{level}}></paper-input>
<!--Effects--> <!--Effects-->
{{> effectsEditList parentId=_id parentCollection="Classes" charId=charId type="class"}} {{> effectsEditList parentId=_id parentCollection="Classes" charId=charId}}
{{> proficiencyEditList parentId=_id parentCollection="Classes" charId=charId}}
{{/baseDialog}} {{/baseDialog}}
{{/with}} {{/with}}
</template> </template>

View File

@@ -1,4 +1,11 @@
Template.classDialog.onRendered(function(){
updatePolymerInputs(this);
});
Template.classDialog.events({ Template.classDialog.events({
"color-change": function(event, instance){
Classes.update(instance.data.classId, {$set: {color: event.color}});
},
"tap #deleteButton": function(event, instance){ "tap #deleteButton": function(event, instance){
Classes.softRemoveNode(instance.data.classId); Classes.softRemoveNode(instance.data.classId);
GlobalUI.deletedToast(instance.data.classId, "Classes", "Class"); GlobalUI.deletedToast(instance.data.classId, "Classes", "Class");
@@ -11,11 +18,11 @@ Template.classDialog.events({
"change #levelValueInput": function(event){ "change #levelValueInput": function(event){
var value = event.currentTarget.value; var value = event.currentTarget.value;
Classes.update(this._id, {$set: {level: value}}); Classes.update(this._id, {$set: {level: value}});
} },
}); });
Template.classDialog.helpers({ Template.classDialog.helpers({
class: function(){ class: function(){
return Classes.findOne(this.classId); return Classes.findOne(this.classId);
} }
}); });

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