Compare commits

...

202 Commits
0.2.5 ... 0.9.3

Author SHA1 Message Date
Stefan Zermatten
be0283a342 Chrome 54 broke transitions again 2016-10-17 14:51:24 +02:00
Stefan Zermatten
e05561dcf9 Trying a different version of webcomponents 2016-10-03 14:27:57 +02:00
Stefan Zermatten
3c776ed018 added webcomponent dependency resolution 2016-10-03 14:19:48 +02:00
Stefan Zermatten
ebecb46935 Fixed drop-down bug, for now
Expect it to break again in Chrome 54
2016-10-03 14:05:30 +02:00
Stefan Zermatten
d83fe917d0 Fixed change logs version numbers 2016-09-19 10:02:25 +02:00
Stefan Zermatten
f526de88a7 Fixed invisible dropdown boxes in Chrome 52. Bumped version 2016-09-08 14:40:47 +02:00
Stefan Zermatten
55adca36d5 Added bower 2016-09-07 10:04:21 +02:00
Stefan Zermatten
be6f54e53a Added Package.json to get Scalingo working 2016-09-07 09:52:14 +02:00
Stefan Zermatten
d41c27c86d Bumped version 2016-06-03 14:49:04 +02:00
Stefan Zermatten
f4397e65ab Global update of packages 2016-06-03 14:43:11 +02:00
Stefan Zermatten
c00c69a0c7 Merge pull request #54 from ThaumRystra/fix-bug-156
closes #54
2016-05-20 09:42:25 +02:00
Robert Perce
6737983782 [#156] add "ritual" after the school in the spell detail view if the spell is, in fact, a ritual. 2016-05-13 11:40:39 -05:00
Stefan Zermatten
78441439c3 Merge pull request #52 from ThaumRystra/user-story-127
Added 'if' function for math.js to use whenever eval-ing input, closes #32
2016-05-04 07:43:21 +02:00
Robert Perce
9b72e1aea2 Added 'if' function for math.js to use whenever eval-ing input 2016-05-03 14:44:40 -05:00
Stefan Zermatten
684eca8603 Merge pull request #51 from ThaumRystra/fix-bug-163
[#163] Changed the x to a × in effectView.js
2016-05-03 11:13:32 +02:00
Stefan Zermatten
868e5f96a4 Update README.md 2016-05-03 09:03:26 +02:00
Stefan Zermatten
6fe368cde7 Updated bourbon and scss 2016-05-03 08:12:44 +02:00
Stefan Zermatten
2ffacb88e0 Included custom vulcanize package in repo 2016-05-03 07:50:12 +02:00
Stefan Zermatten
af3af0e550 Fixed missing bower dependency 2016-05-03 07:48:14 +02:00
Robert Perce
81f50d94c1 [#163] Changed the x to a × in effectView.js 2016-04-22 15:20:32 -05:00
Stefan Zermatten
49e6d51b95 Merge pull request #40 from ThaumRystra/feature-coins-inc-default
Make coins incrementable by default, closes #27
2016-03-09 14:17:23 +02:00
Stefan Zermatten
1e4e1a3b11 Merge pull request #38 from ThaumRystra/feature-base-ability-scores-10
Base ability scores default to 10, closes #36
2016-03-09 13:56:36 +02:00
Stefan Zermatten
4440c88417 Removed grouping from effects
Grouping is currently only used to differentiate effects that have the same parent, but are ambiguous as to which part of that parent they come from, like race or background. Features do not need grouped effects, because there can be no ambiguity.
2016-03-09 12:57:23 +02:00
Connor Petersen
98047ca806 Moved base-10 default to Features.js, cleaned up default Effects, and Features code to be consistent. 2016-03-07 23:21:42 -08:00
Connor Petersen
051cabc712 Cleaned up a little more 2016-03-07 22:53:27 -08:00
Connor Petersen
636fa504f1 Updated to not require caching the id or making a separate update call 2016-03-07 22:51:41 -08:00
Connor Petersen
ff9fc916f6 Added a call to adding a feature and 6 related effects to the newCharacterDialog event handler 2016-02-11 23:29:27 -08:00
Connor Petersen
2206a607b2 Added an update call to default Item adding function to update the showIncrement field to true for coins 2016-02-10 22:11:41 -08:00
Stefan Zermatten
ce224301b2 Fixed layout on chrome 49+, made character names into links 2016-02-01 09:34:22 +02:00
Stefan Zermatten
e6a9911dfc Replaced the head that was accidentally deleted 2015-11-19 12:44:11 +02:00
Stefan Zermatten
8a1871ee18 Fixed character list FAB not staying put 2015-11-19 12:43:54 +02:00
Stefan Zermatten
402f885f85 Specified thaum:vulcanize at 0.5 2015-11-18 08:52:52 +02:00
Stefan Zermatten
d07c118d47 Removed useless public items, removed Appcache, fixed vulcanize 2015-11-17 15:20:33 +02:00
Stefan Zermatten
103d44eeec Merge branch 'feature-fast-render' 2015-11-17 13:59:22 +02:00
Stefan Zermatten
33196c6771 Bumped change log 2015-11-17 13:58:50 +02:00
Stefan Zermatten
80dc862047 Added Appcache 2015-11-17 12:26:55 +02:00
Stefan Zermatten
314da14ad1 Added indexes to charId on character fields 2015-11-02 09:35:05 +02:00
Stefan Zermatten
e5dbe81ac1 Added fast render support to routes which have subscriptions
Closes #18
2015-10-13 08:22:00 +02:00
Stefan Zermatten
7e68ef64cc Merge pull request #16 from ThaumRystra/bugfix-encumberance-meter-swapping
Change character reference to be reactive based on the active, undestroyed template
2015-10-09 08:58:53 +02:00
Connor Petersen
c9d71cad52 Change character reference to be reactive based on the active, undestroyed template 2015-10-08 23:05:57 -07:00
Stefan Zermatten
d79a808c81 Bumped Version 2015-10-05 08:59:25 +02:00
Stefan Zermatten
1016c39bdf Merge branch 'fix-www-route' 2015-10-05 08:59:07 +02:00
Stefan Zermatten
5f4923e049 Added canonical URL 2015-10-05 08:57:50 +02:00
Stefan Zermatten
e83237a728 Added demeteorized to gitignore 2015-10-05 08:57:33 +02:00
Stefan Zermatten
9f323738bf Renamed custom useraccount-polymer package to be more obvious 2015-10-05 08:34:43 +02:00
Stefan Zermatten
1fc76fa50d Removed Vulcanize entirely
There is no version compatible with both Meteor 1.2 and Polymer 0.5, so screw it, clients can deal with a few ms more load time.
2015-10-05 08:34:18 +02:00
Stefan Zermatten
36d5ff0a88 Update Meteor to 1.2
This requires local copies of packages that are broken or not Polymer 0.5

Closes #15
2015-10-03 22:40:04 +02:00
Stefan Zermatten
d05874ed13 Bumped version 2015-10-01 08:06:29 +02:00
Stefan Zermatten
df2521e69c Merge pull request #5 from ThaumRystra/bugfix-unprepared-spell-attacks
Spell child attacks now set their enabled state to match the parent spell prepared state
2015-10-01 08:03:30 +02:00
Stefan Zermatten
3c6a685fe8 Fixed reference to parentId which should be parent.id 2015-10-01 07:45:38 +02:00
Connor Petersen
a77e560284 Spell attacks correctly enable based on preparedness, Model correction with collection hooks 2015-09-30 13:30:59 -07:00
Stefan Zermatten
4cec83918f Updated README.md to remove out of date info 2015-09-28 07:35:10 +02:00
Stefan Zermatten
fec95c51c6 Rolled back changes to spells tab, It wasn't a column-layout 2015-09-25 13:03:14 +02:00
Stefan Zermatten
425c42d049 Bumped version 2015-09-25 12:52:28 +02:00
Stefan Zermatten
ab6f0c4f5b Merge branch 'fix-columns' 2015-09-25 12:51:04 +02:00
Stefan Zermatten
5d6e57b896 Wrap cards in padded divs to make columns behave 2015-09-25 12:49:48 +02:00
Stefan Zermatten
7c0a8125f2 Merge branch 'fix-carry-capacity-effectView' 2015-09-04 13:56:06 +02:00
Stefan Zermatten
7481ef08a8 Carry capacity effects no longer show up as "no stat" when viewed 2015-09-04 13:55:42 +02:00
Stefan Zermatten
b578dd5fb0 Merge branch 'feature-carry-capacity-modifier' 2015-09-03 14:09:51 +02:00
Stefan Zermatten
5d6f934d88 Bumped version 2015-09-03 14:09:23 +02:00
Stefan Zermatten
337f0bfa8a Made sure migration touches every character 2015-09-03 14:09:17 +02:00
Stefan Zermatten
c62784894b Made sure encumbered conditions respect carry capacity 2015-09-03 13:53:00 +02:00
Stefan Zermatten
75fff43d7d Gave an effect menu option for carry capacity 2015-09-03 13:52:40 +02:00
Stefan Zermatten
a9eeeac0df Fixed carry capacity bar 2015-09-03 13:52:22 +02:00
Stefan Zermatten
c8af0ff0a9 Fixed carry capacity table 2015-09-03 13:52:06 +02:00
Stefan Zermatten
9e200db7b9 Made carry capacity an attribute, migrations need testing 2015-08-31 15:51:52 +02:00
Stefan Zermatten
c08cf83096 Bumped version 2015-08-27 12:21:23 +02:00
Stefan Zermatten
d9368b06d0 Merge branch 'bugfix-0.6.8' 2015-08-27 12:07:42 +02:00
Stefan Zermatten
2703367681 Proficiencies now get disabled when their features are disabled 2015-08-27 12:05:56 +02:00
Stefan Zermatten
d419442549 Fixed share dialog not finding usernames or email addresses 2015-08-27 11:59:38 +02:00
Stefan Zermatten
99df01c950 Prevented negative temporary hitpoints being added 2015-08-27 11:43:34 +02:00
Stefan Zermatten
d76349b3bb Merge branch 'release-0.6.7a' 2015-07-29 09:57:06 +02:00
Stefan Zermatten
39c061f4e8 Fixed formatting of not found page, now has a toolbar and centered text 2015-07-29 09:55:22 +02:00
Stefan Zermatten
6d167ddb22 Fixed formatting of loading page, more padding 2015-07-29 09:55:01 +02:00
Stefan Zermatten
037acbd459 Added Starter Set Wizard to the front page 2015-07-29 09:54:43 +02:00
Stefan Zermatten
4d3fc3bb09 Fixed accidental back() requests when closing detail dialogs 2015-07-29 09:54:18 +02:00
Stefan Zermatten
4b984d4fac Made analytics show all characters as the same page 2015-07-29 09:53:46 +02:00
Stefan Zermatten
58843613ba Merge branch 'release-0.6.7' 2015-07-27 13:12:42 +02:00
Stefan Zermatten
39b549b24b Bumped version 2015-07-27 13:12:28 +02:00
Stefan Zermatten
c79177de72 Added Google Analytics 2015-07-27 13:10:33 +02:00
Stefan Zermatten
11d09b1487 Fixed effect values not being visible on mobile 2015-07-27 12:16:02 +02:00
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
85b63f152f Merge branch 'release-0.6.6' into develop 2015-07-27 11:23:12 +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
237 changed files with 6321 additions and 2211 deletions

View File

@@ -1,58 +1,13 @@
TODO
====
RPG Docs
========
* Get Polymer installed using bower.
* Install Vulcanize package listed below
* Copy the differential polymer demo to get polymer implemented nicely
* Update Meteor
* Install and use LESS
This is the repo for [DiceCloud](dicecloud.com). The currently deployed version should always be the latest release of the master branch.
Packages used
=============
Getting started
---------------
* meteor-platform
* Base Meteor.
* [Docs](http://docs.meteor.com/#/full/)
* autopublish
* Publishes everything to the client.
* Must be removed before release
* insecure
* Allows the client the freedom to modify any colleciton.
* Must be removed before release
* iron:router
* Enables pagination and URL's to direct to specific templates.
* [Tutorial](http://www.manuel-schoebel.com/blog/iron-router-tutorial)
* accounts-password
* Lets users create accounts with a simple password
* accounts-ui
* Adds simple UI for logging in
* random
* Somewhat decent cryptographically strong psuedo random number generation.
* [readme](https://atmospherejs.com/meteor/random)
* dburles:collection-helpers
* Adds template-style helpers to collections. [github page](https://github.com/dburles/meteor-collection-helpers)
* reactive-var
* Friendly reactive variables
* [Meteor Docs](http://docs.meteor.com/#/full/reactivevar_pkg)
* cw4gn3r:jquery-event-drag
* Adds jquery drag events
* underscore
* Handy javascript utilities
* [Docs](http://underscorejs.org/)
* aldeed:collection2
* Extends collections with Schemas
* [(gitHub page)](https://github.com/aldeed/meteor-collection2)
* uses [SimpleSchema](https://github.com/aldeed/meteor-simple-schema)
* aldeed:autoform
* Automatically generates bootstrap forms for collection2 Schemas.
* [github](https://github.com/aldeed/meteor-autoform)
* differential:vulcanize
* Bakes all the polymer imports into one file
* [github](https://github.com/Differential/meteor-vulcanize)
************
Resources
=========
[differential's polymer demo](https://github.com/Differential/polymer-demo)
`git clone https://github.com/ThaumRystra/RPG-Docs RPG-Docs`
`cd RPG-Docs`
`cd rpg-docs`
`bower install`
`meteor`

5
rpg-docs/.gitignore vendored
View File

@@ -1,5 +1,8 @@
.meteor/local
.meteor/meteorite
.demeteorized
settings.json
public/components
nohup.out
dump
node_modules
dump

View File

@@ -6,3 +6,8 @@ notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package

View File

@@ -1 +0,0 @@

View File

@@ -3,7 +3,7 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-platform
thaum:vulcanize@0.0.5
iron:router
accounts-password
accounts-ui
@@ -12,11 +12,9 @@ dburles:collection-helpers
reactive-var
underscore
aldeed:collection2
differential:vulcanize
matb33:collection-hooks
zimme:collection-softremovable
momentjs:moment
mike:mocha
dburles:mongo-collection-instances
percolate:migrations
ecwyne:mathjs
@@ -24,3 +22,26 @@ useraccounts:polymer
accounts-google
splendido:accounts-meld
email
meteorhacks:subs-manager
meteorhacks:kadira
chuangbo:marked
reywood:iron-router-ga
meteor-base
mobile-experience
mongo
blaze-html-templates
session
jquery
tracker
logging
reload
ejson
spacebars
check
useraccounts:iron-routing
wizonesolutions:canonical
meteorhacks:fast-render
fourseven:scss
wolves:bourbon
standard-minifier-css
standard-minifier-js

View File

@@ -1 +1 @@
METEOR@1.1.0.2
METEOR@1.3.2.4

View File

@@ -1,101 +1,131 @@
accounts-base@1.2.0
accounts-google@1.0.4
accounts-oauth@1.1.5
accounts-password@1.1.1
accounts-ui@1.1.5
accounts-ui-unstyled@1.1.7
aldeed:collection2@2.3.3
aldeed:simple-schema@1.3.3
amplify@1.0.0
autoupdate@1.2.1
base64@1.0.3
binary-heap@1.0.3
blaze@2.1.2
blaze-tools@1.0.3
boilerplate-generator@1.0.3
callback-hook@1.0.3
check@1.0.5
coffeescript@1.0.6
dburles:collection-helpers@1.0.3
dburles:mongo-collection-instances@0.3.3
ddp@1.1.0
deps@1.0.7
differential:vulcanize@0.0.5
accounts-base@1.2.7
accounts-google@1.0.9
accounts-oauth@1.1.12
accounts-password@1.1.8
accounts-ui@1.1.9
accounts-ui-unstyled@1.1.12
aldeed:collection2@2.9.1
aldeed:collection2-core@1.1.1
aldeed:schema-deny@1.0.1
aldeed:schema-index@1.0.1
aldeed:simple-schema@1.5.3
allow-deny@1.0.4
autoupdate@1.2.9
babel-compiler@6.6.4
babel-runtime@0.1.8
base64@1.0.8
binary-heap@1.0.8
blaze@2.1.7
blaze-html-templates@1.0.4
blaze-tools@1.0.8
boilerplate-generator@1.0.8
caching-compiler@1.0.4
caching-html-compiler@1.0.6
callback-hook@1.0.8
check@1.2.1
chuangbo:cookie@1.1.0
chuangbo:marked@0.3.5_1
coffeescript@1.0.17
dburles:collection-helpers@1.0.4
dburles:mongo-collection-instances@0.3.5
ddp@1.2.5
ddp-client@1.2.7
ddp-common@1.2.5
ddp-rate-limiter@1.0.4
ddp-server@1.2.6
deps@1.0.12
diff-sequence@1.0.5
ecmascript@0.4.3
ecmascript-runtime@0.2.10
ecwyne:mathjs@0.25.0
ejson@1.0.6
email@1.0.6
fastclick@1.0.3
geojson-utils@1.0.3
google@1.1.5
html-tools@1.0.4
htmljs@1.0.4
http@1.1.0
id-map@1.0.3
iron:controller@1.0.7
iron:core@1.0.7
iron:dynamic-template@1.0.7
iron:layout@1.0.7
iron:location@1.0.7
iron:middleware-stack@1.0.7
iron:router@1.0.7
iron:url@1.0.7
jquery@1.11.3_2
json@1.0.3
lai:collection-extensions@0.1.3
launch-screen@1.0.2
less@1.0.14
livedata@1.0.13
localstorage@1.0.3
logging@1.0.7
matb33:collection-hooks@0.7.13
meteor@1.1.6
meteor-platform@1.2.2
mike:mocha@0.5.4
minifiers@1.1.5
minimongo@1.0.8
mobile-status-bar@1.0.3
momentjs:moment@2.10.3
mongo@1.1.0
npm-bcrypt@0.7.8_2
oauth@1.1.4
oauth2@1.1.3
observe-sequence@1.0.6
ordered-dict@1.0.3
package-version-parser@3.0.3
percolate:migrations@0.7.5
practicalmeteor:chai@1.9.2_3
practicalmeteor:loglevel@1.1.0_3
random@1.0.3
reactive-dict@1.1.0
reactive-var@1.0.5
reload@1.1.3
retry@1.0.3
routepolicy@1.0.5
sanjo:long-running-child-process@1.0.3
sanjo:meteor-files-helpers@1.1.0_4
sanjo:meteor-version@1.0.0
service-configuration@1.0.4
session@1.1.0
sha@1.0.3
softwarerero:accounts-t9n@1.0.9
spacebars@1.0.6
spacebars-compiler@1.0.6
ejson@1.0.11
email@1.0.12
fastclick@1.0.11
fourseven:scss@3.4.3
geojson-utils@1.0.8
google@1.1.11
hot-code-push@1.0.4
html-tools@1.0.9
htmljs@1.0.9
http@1.1.5
id-map@1.0.7
iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.1.0
iron:router@1.0.12
iron:url@1.0.11
jquery@1.11.8
lai:collection-extensions@0.2.1_1
launch-screen@1.0.11
less@2.6.0
livedata@1.0.18
localstorage@1.0.9
logging@1.0.12
matb33:collection-hooks@0.8.1
mdg:validation-error@0.2.0
meteor@1.1.14
meteor-base@1.0.4
meteorhacks:fast-render@2.14.0
meteorhacks:inject-data@2.0.0
meteorhacks:kadira@2.28.7
meteorhacks:meteorx@1.4.1
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
minifier-css@1.1.11
minifier-js@1.1.11
minimongo@1.0.16
mobile-experience@1.0.4
mobile-status-bar@1.0.12
modules@0.6.1
modules-runtime@0.6.3
momentjs:moment@2.13.1
mongo@1.1.7
mongo-id@1.0.4
mongo-livedata@1.0.12
npm-bcrypt@0.8.5
npm-mongo@1.4.43
oauth@1.1.10
oauth2@1.1.9
observe-sequence@1.0.11
ordered-dict@1.0.7
percolate:migrations@0.9.8
promise@0.6.7
raix:eventemitter@0.1.3
random@1.0.9
rate-limit@1.0.4
reactive-dict@1.1.7
reactive-var@1.0.9
reload@1.1.8
retry@1.0.7
reywood:iron-router-ga@0.7.1
routepolicy@1.0.10
service-configuration@1.0.9
session@1.1.5
sha@1.0.7
softwarerero:accounts-t9n@1.3.4
spacebars@1.0.11
spacebars-compiler@1.0.11
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.0
srp@1.0.3
templating@1.1.1
tracker@1.0.7
ui@1.0.6
underscore@1.0.3
url@1.0.4
useraccounts:core@1.9.1
useraccounts:polymer@1.9.1
velocity:chokidar@0.12.6_1
velocity:core@0.6.1
velocity:html-reporter@0.5.3
velocity:meteor-internals@1.1.0_7
velocity:shim@0.1.0
webapp@1.2.0
webapp-hashing@1.0.3
zimme:collection-behaviours@1.0.4
zimme:collection-softremovable@1.0.4
splendido:accounts-meld@1.3.1
srp@1.0.8
standard-minifier-css@1.0.6
standard-minifier-js@1.0.6
templating@1.1.9
templating-tools@1.0.4
thaum:vulcanize@0.0.5
tracker@1.0.13
ui@1.0.11
underscore@1.0.8
url@1.0.9
useraccounts:core@1.14.2
useraccounts:iron-routing@1.14.2
useraccounts:polymer@1.12.3
webapp@1.2.8
webapp-hashing@1.0.9
wizonesolutions:canonical@0.0.5
wolves:bourbon@3.1.0
zimme:collection-behaviours@1.1.3
zimme:collection-softremovable@1.0.5

View File

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

View File

@@ -7,6 +7,7 @@ Schemas.Attack = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
@@ -24,18 +25,12 @@ Schemas.Attack = new SimpleSchema({
optional: true,
trim: false,
},
damageBonus: {
damage: {
type: String,
defaultValue: "strengthMod",
defaultValue: "1d8 + {strengthMod}",
optional: true,
trim: false,
},
damageDice: {
type: String,
optional: true,
defaultValue: "1d8",
allowedValues: DAMAGE_DICE,
},
damageType: {
type: String,
allowedValues: [
@@ -75,5 +70,17 @@ Attacks.attachSchema(Schemas.Attack);
Attacks.attachBehaviour("softRemovable");
makeChild(Attacks, ["name", "enabled"]); //children of lots of things
Attacks.after.insert(function (userId, attack) {
//Check to see if this attack's parent is a spell, if so, mirror prepared state to enabled
if (attack.parent.collection === "Spells") {
var parentSpell = Spells.findOne(attack.parent.id);
if (parentSpell.prepared === "unprepared") {
Attacks.update(attack._id, {$set: {enabled: false}});
} else if (parentSpell.prepared === "prepared" || "always") {
Attacks.update(attack._id, {$set: {enabled: true}});
}
}
});
Attacks.allow(CHARACTER_SUBSCHEMA_ALLOW);
Attacks.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,28 +1,52 @@
Buffs = new Mongo.Collection("buffs");
//buffs are temporary once applied and store things which expire and their expiry time
Schemas.Buff = new SimpleSchema({
//buff id
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue: function(){
if (!this.isSet) return Random.id();
},
},
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
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",
},
//expiry time
expiry: {type: Number, optional: true},
duration: {type: Number},
});
Buffs.attachSchema(Schemas.Buff);
Buffs.attachBehaviour("softRemovable");
makeParent(Buffs, "name"); //parents of effects and attacks
makeParent(Buffs, ["name", "enabled"]); //parents of effects
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -3,16 +3,17 @@ Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({
//strings
name: {type: String, defaultValue: "", trim: false},
alignment: {type: String, defaultValue: "", trim: false},
gender: {type: String, defaultValue: "", trim: false},
race: {type: String, defaultValue: "", trim: false},
description: {type: String, defaultValue: "", trim: false},
personality: {type: String, defaultValue: "", trim: false},
ideals: {type: String, defaultValue: "", trim: false},
bonds: {type: String, defaultValue: "", trim: false},
flaws: {type: String, defaultValue: "", trim: false},
backstory: {type: String, defaultValue: "", trim: false},
name: {type: String, defaultValue: "", trim: false, optional: true},
alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: {type: String, defaultValue: "", trim: false, optional: true},
race: {type: String, defaultValue: "", trim: false, optional: true},
picture: {type: String, defaultValue: "", trim: true, optional: true},
description: {type: String, defaultValue: "", trim: false, optional: true},
personality: {type: String, defaultValue: "", trim: false, optional: true},
ideals: {type: String, defaultValue: "", trim: false, optional: true},
bonds: {type: String, defaultValue: "", trim: false, optional: true},
flaws: {type: String, defaultValue: "", trim: false, optional: true},
backstory: {type: String, defaultValue: "", trim: false, optional: true},
//attributes
//ability scores
@@ -32,6 +33,7 @@ Schemas.Character = new SimpleSchema({
age: {type: Schemas.Attribute},
ageRate: {type: Schemas.Attribute},
armor: {type: Schemas.Attribute},
carryMultiplier: {type: Schemas.Attribute},
//resources
level1SpellSlots: {type: Schemas.Attribute},
@@ -159,6 +161,7 @@ Schemas.Character = new SimpleSchema({
deathSave: {type: Schemas.DeathSave},
//permissions
party: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true},
owner: {type: String, regEx: SimpleSchema.RegEx.Id},
readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
@@ -168,61 +171,122 @@ Schemas.Character = new SimpleSchema({
defaultValue: "q",
},
//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);
var attributeBase = function(charId, statName){
var attributeBase = preventLoop(function(charId, statName){
check(statName, String);
var effects = Effects.find(
{charId: charId, stat: statName, enabled: true}
).fetch();
effects = _.groupBy(effects, "operation");
var value = _.contains(DAMAGE_MULTIPLIERS, statName) ? 1 : 0;
//if it's a damage multiplier, we treat it specially
if (_.contains(DAMAGE_MULTIPLIERS, statName)){
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
_.each(effects.base, function(effect){
var efv = evaluateEffect(charId, effect);
if (efv > value){
value = efv;
Effects.find({
charId: charId,
stat: statName,
enabled: true,
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
_.each(effects.add, function(effect){
value += evaluateEffect(charId, effect);
});
var result = (base + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
//multiply all the mul values
_.each(effects.mul, function(effect){
value *= evaluateEffect(charId, effect);
});
return Math.floor(result);
});
//ensure value is >= all mins
_.each(effects.min, function(effect){
var min = evaluateEffect(charId, effect);
value = value > min ? value : min;
if (Meteor.isClient) {
Template.registerHelper("characterCalculate", function(func, charId, input) {
try {
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;
//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;
}, "");
});
return value;
};
//functions and calculated values.
//These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters
Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(this._id, {fields: fieldSelector});
var char = Characters.findOne(charId, {fields: fieldSelector});
var field = char[fieldName];
if (field === undefined){
throw new Meteor.Error(
@@ -235,8 +299,7 @@ Characters.helpers({
}
return field;
},
//returns the value of a field
fieldValue : function(fieldName){
fieldValue: function(charId, fieldName) {
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
@@ -246,102 +309,92 @@ Characters.helpers({
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return this.skillMod(fieldName);
return Characters.calculate.skillMod(charId, fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return this.attributeValue(fieldName);
return Characters.calculate.attributeValue(charId, fieldName);
}
//fall back to just returning the field itself
return this.getField(fieldName);
return Characters.calculate.getField(charId, fieldName);
},
attributeValue: function(attributeName){
var charId = this._id;
var attribute = this.getField(attributeName);
attributeValue: memoize(function(charId, attributeName){
var attribute = Characters.calculate.getField(charId, attributeName);
//base value
var value = this.attributeBase(attributeName);
var value = Characters.calculate.attributeBase(charId, attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
},
attributeBase: preventLoop(function(attributeName){
var charId = this._id;
//base value
}),
attributeBase: memoize(function(charId, attributeName){
return attributeBase(charId, attributeName);
}),
skillMod: preventLoop(function(skillName){
var charId = this._id;
var skill = this.getField(skillName);
skillMod: memoize(preventLoop(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
//get the final value of the ability score
var ability = this.attributeValue(skill.ability);
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 = this.proficiency(skillName);
var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier
mod += prof * this.attributeValue("proficiencyBonus");
mod += prof * Characters.calculate.attributeValue(charId, "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);
}),
var value;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
proficiency: function(skillName){
var charId = this._id;
//return largest value in proficiency array
var prof = 0;
Proficiencies.find(
{charId: charId, name: skillName, enabled: true}
).forEach(function(proficiency){
var newProf = proficiency.value;
if (newProf > prof){
prof = newProf;
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;
}
});
return prof;
},
var result = (mod + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
passiveSkill: function(skillName){
if (_.isString(skillName)){
var skill = this.getField(skillName);
}
var charId = this._id;
var mod = +this.skillMod(skillName);
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);
});
return value;
//TODO decide whether (dis)advantage gives (-)+5 to passive checks
},
advantage: function(skillName){
var charId = this._id;
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();
@@ -351,19 +404,18 @@ Characters.helpers({
if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1;
return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
},
abilityMod: function(attribute){
return signedString(getMod(this.attributeValue(attribute)));
},
passiveAbility: function(attribute){
var mod = +getMod(this.attributeValue(attribute));
passiveAbility: function(charId, attribute){
var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return 10 + mod;
},
xpLevel: function(){
var xp = this.experience();
xpLevel: function(charId){
var xp = Characters.calculate.experience(charId);
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
return i;
@@ -372,30 +424,103 @@ Characters.helpers({
if (xp > 355000) return 20;
return 0;
},
level: function(){
level: memoize(function(charId){
var level = 0;
Classes.find({charId: this._id}).forEach(function(cls){
Classes.find({charId: charId}).forEach(function(cls){
level += cls.level;
});
return level;
},
experience: function(){
}),
experience: memoize(function(charId){
var xp = 0;
Experiences.find(
{charId: this._id},
{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.
//These functions can only rely on this._id since no other
//field is likely to be attached to all returned characters
Characters.helpers({
//returns the value stored in the field requested
//will set up dependencies on just that field
getField : function(fieldName){
depreciated();
return Characters.calculate.getField(this._id, fieldName);
},
//returns the value of a field
fieldValue : function(fieldName){
depreciated();
return Characters.calculate.fieldValue(this._id, fieldName);
},
attributeValue: function(attributeName){
depreciated();
return Characters.calculate.attributeValue(this._id, attributeName);
},
attributeBase: function(attributeName){
depreciated();
return Characters.calculate.attributeBase(this._id, attributeName);
},
skillMod: function(skillName){
depreciated();
return Characters.calculate.skillMod(this._id, skillName);
},
proficiency: function(skillName){
depreciated();
return Characters.calculate.proficiency(this._id, skillName);
},
passiveSkill: function(skillName){
depreciated();
return Characters.calculate.passiveSkill(this._id, skillName);
},
advantage: function(skillName){
depreciated();
return Characters.calculate.advantage(this._id, skillName);
},
abilityMod: function(attribute){
depreciated();
return Characters.calculate.abilityMod(this._id, attribute);
},
passiveAbility: function(attribute){
depreciated();
return Characters.calculate.passiveAbility(this._id, attribute);
},
xpLevel: function(){
depreciated();
return Characters.calculate.xpLevel(this._id);
},
level: function(){
depreciated();
return Characters.calculate.level(this._id);
},
experience: function(){
depreciated();
return Characters.calculate.experience(this._id);
},
});
//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});
Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id});
@@ -408,8 +533,8 @@ Characters.after.remove(function(userId, character) {
SpellLists .remove({charId: character._id});
Items .remove({charId: character._id});
Containers .remove({charId: character._id});
}
});
});
}
Characters.allow({
insert: function(userId, doc) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ Schemas.Proficiency = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
@@ -30,7 +31,7 @@ Schemas.Proficiency = new SimpleSchema({
Proficiencies.attachSchema(Schemas.Proficiency);
Proficiencies.attachBehaviour("softRemovable");
makeChild(Proficiencies);
makeChild(Proficiencies, ["enabled"]);
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);

View File

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

View File

@@ -1,7 +1,7 @@
Spells = new Mongo.Collection("spells");
Schemas.Spell = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
prepared: {
type: String,
defaultValue: "prepared",
@@ -62,6 +62,23 @@ Spells.attachSchema(Schemas.Spell);
Spells.attachBehaviour("softRemovable");
makeChild(Spells); //children of spell lists
makeParent(Spells, ["name", "enabled"]); //parents of attacks
Spells.after.update(function (userId, spell, fieldNames) {
//Update prepared state of spell and child attacks to be enabled or not
if (_.contains(fieldNames, "prepared")) {
var childAttacks = Attacks.find({"parent.id": spell._id}).fetch();
if (spell.prepared === "unprepared") {
_.each(childAttacks, function(attack){
Attacks.update(attack._id, {$set: {enabled: false}});
});
} else if (spell.prepared === "prepared" || "always") {
_.each(childAttacks, function(attack){
Attacks.update(attack._id, {$set: {enabled: true}});
});
}
}
});
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
Spells.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -3,10 +3,6 @@
* Damage, healing and resource cost/recovery are all adjustments
*/
Schemas.Adjustment = new SimpleSchema({
name: {
type: String,
optional: true,
},
//which stat the adjustment is applied to
stat: {
type: String,

View File

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

View File

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

View File

@@ -4,12 +4,13 @@ Schemas.Item = new SimpleSchema({
name: {type: String, defaultValue: "New Item", trim: false},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id}, //id of owner
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
quantity: {type: Number, min: 0, defaultValue: 1},
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
value: {type: Number, min: 0, defaultValue: 0, decimal: true},
enabled: {type: Boolean, defaultValue: false},
requiresAttunement: {type: Boolean, defaultValue: false},
"settings.showIncrement": {type: Boolean, defaultValue: false},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
@@ -19,6 +20,156 @@ Schemas.Item = new SimpleSchema({
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({
totalValue: function(){
return this.value * this.quantity;
@@ -33,103 +184,6 @@ Items.helpers({
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){
@@ -174,6 +228,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Silver piece",
@@ -187,6 +244,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Copper piece",
@@ -200,6 +260,9 @@ Characters.after.insert(function(userId, char) {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
}
});

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,10 +1,14 @@
Router.configure({
loadingTemplate: "loading",
layoutTemplate: "layout",
trackPageView: true,
});
Router.plugin("ensureSignedIn", {
except: ["home", "atSignIn", "atSignUp", "atForgotPassword", "notFound"]
only: [
"profile",
"characterList",
]
});
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
@@ -12,34 +16,38 @@ Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
Router.map(function() {
this.route("/", {
name: "home",
onAfterAction: function() {
document.title = appName;
},
});
this.route("characterList", {
path: "/characterList",
waitOn: function(){
return Meteor.subscribe("characterList", Meteor.userId());
return subsManager.subscribe("characterList", Meteor.userId());
},
data: {
characters: function(){
return Characters.find({}, {fields: {_id: 1}});
return Characters.find({}, {fields: {_id: 1}, sort: {name: 1}});
}
},
onAfterAction: function() {
document.title = appName;
},
fastRender: true,
});
this.route("characterSheet", {
path: "/character/:_id",
waitOn: function(){
return [
Meteor.subscribe("singleCharacter", this.params._id, Meteor.userId()),
subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1}}
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
@@ -50,6 +58,13 @@ Router.map(function() {
document.title = name;
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/character");
this.next();
},
fastRender: true,
});
this.route("loading", {
@@ -63,7 +78,28 @@ Router.map(function() {
},
});
this.route("/loginButtons", {
name: "loginButtons",
})
this.route("/changelog", {
name: "changeLog",
waitOn: function() {
return [
subsManager.subscribe("changeLog"),
]
},
data: {
changeLogs: function() {
return ChangeLogs.find({}, {sort: {version: -1}});
}
},
onAfterAction: function() {
document.title = appName;
},
fastRender: true,
});
this.route("/guide", {
name: "guide",
onAfterAction: function() {
document.title = appName;
},
});
});

View File

@@ -2,7 +2,9 @@
"name": "RPG Docs",
"version": "0.0.0",
"homepage": "",
"authors": ["Stefan Zermatten"],
"authors": [
"Stefan Zermatten"
],
"license": "none",
"private": true,
"ignore": [
@@ -13,14 +15,12 @@
"tests"
],
"dependencies": {
"polymer": "Polymer/polymer#~0.5.4",
"core-elements": "Polymer/core-elements#~0.5.4",
"paper-elements": "Polymer/paper-elements#~0.5.4",
"paper-fab-menu": "cwdoh/paper-fab-menu"
"polymer": "Polymer/polymer#~0.5.5",
"core-elements": "Polymer/core-elements#~0.5.5",
"paper-elements": "Polymer/paper-elements#~0.5.5",
"paper-fab-menu": "cwdoh/paper-fab-menu#~0.4.0"
},
"resolutions": {
"core-component-page": "^0.5.0",
"polymer": "^0.5.0",
"webcomponentsjs": "^0.5.0"
"webcomponentsjs": "0.6.3"
}
}
}

View File

@@ -77,10 +77,10 @@ this.GlobalUI = (function() {
var throttleBack = _.throttle(function() {
history.back();
}, 800, {trailing: false});
}, 100, {trailing: false});
GlobalUI.closeDetail = function() {
if (!!(window.history && window.history.pushState)) {
if (window.history && history.pushState && history.state.detail === "opened") {
throttleBack();
} else {
Session.set("global.ui.detailShow", false);

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

@@ -20,6 +20,11 @@ openParentDialog = function(parent, charId, heroId) {
template: "itemDialog",
data: {itemId: parent.id},
};
} else if (parent.collection === "Spells") {
detail = {
template: "spellDialog",
data: {spellId: parent.id},
};
}
detail.heroId = heroId;
detail.charId = charId;

View File

@@ -1,25 +1,27 @@
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 = [];
//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.push(sp + "sp");
}
//cp
var cp = 10 * ((value * 10) % 1);
cp = Math.round(cp * 1000) / 1000;
if (cp > 0) {
resultArray.push(cp + "cp");
}
//build string with correct spacing
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
if (i !== 0) {
result += " ";

View File

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

View File

@@ -70,8 +70,8 @@
background-color: #9E9E9E;
}
.blue-grey {
background-color: #607D8B;
.app-grey {
background-color: #424242;
}
.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 "{wolves: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: fixed;
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,24 @@
.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;
}
html /deep/ paper-dropdown {
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.37);
}

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,268 +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;
}
.summaryTable td{
text-align: right;
padding: 4px;
min-width: 80px;
}
.summaryTable td:first-child {
text-align: left;
}
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;
}
core-item {
cursor: pointer;
}
.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);
}
.clickable, .statCard, .abilityMiniCard {
cursor: pointer;
}
.skillRow {
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;
}
.wideTable td {
padding: 4px 8px 4px 8px;
}
.wideTable table {
padding: 8px;
}
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 {
cursor: initial;
border-bottom: black solid 0.5px;
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;
}
.sideMargin {
margin-left: 16px;
margin-right: 16px;
}
.vertMargin {
margin-top: 16px;
margin-bottom: 16px;
}
.spaceAfter {
margin-bottom: 8px;
}
.s {
padding: 0 0 16px 0;
}
.leftRound{
border-radius: 2px 0 0 2px;
}
.preline {
white-space: pre-line;
}
.prewrap{
white-space: pre-wrap;
}

View File

@@ -18,7 +18,7 @@
letter-spacing: 0;
}
.white-text .display1{
.white-text .display1, .white-text.display1{
color: rgba(255,255,255,0.54);
}

View File

@@ -3,36 +3,26 @@
<div layout vertical flex>
<div layout horizontal>
<!--attackBonus-->
<paper-input id="attackBonusInput"
<paper-input class="attackBonusInput"
label="Attack Bonus"
floatinglabel
value={{attackBonus}}
flex></paper-input>
<!--details-->
<paper-input id="detailInput"
<paper-input class="detailInput"
label="Details"
floatinglabel
value={{details}}></paper-input>
</div>
<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-->
<paper-input id="damageInput"
label="Damage Bonus"
<paper-input class="damageInput"
label="Damage"
floatinglabel
value={{damageBonus}}
value={{damage}}
flex></paper-input>
<!--DamageType-->
<paper-dropdown-menu id="damageTypeDropdown" label="Damage Type">
<paper-dropdown-menu class="damageTypeDropdown" label="Damage Type">
<paper-dropdown layered class="dropdown">
<core-menu class="menu" selected={{damageType}}>
{{#each damageTypes}}
@@ -44,6 +34,6 @@
</div>
</div>
<!--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>
</template>

View File

@@ -1,38 +1,43 @@
var damageTypes = ["bludgeoning", "piercing", "slashing",
"acid", "cold", "fire", "force", "lightning", "necrotic",
"poison", "psychic", "radiant", "thunder"];
var damageTypes = [
"bludgeoning",
"piercing",
"slashing",
"acid",
"cold",
"fire",
"force",
"lightning",
"necrotic",
"poison",
"psychic",
"radiant",
"thunder",
];
Template.attackEdit.events({
"tap #deleteAttack": function(event, instance) {
"tap .deleteAttack": function(event, instance) {
Attacks.softRemoveNode(this._id);
GlobalUI.deletedToast(this._id, "Attacks", "Attack");
},
"change #attackBonusInput": function(event) {
"change .attackBonusInput": function(event) {
var value = event.currentTarget.value;
Attacks.update(this._id, {$set: {attackBonus: value}});
},
"change #damageInput": function(event) {
"change .damageInput": function(event) {
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;
Attacks.update(this._id, {$set: {details: value}});
},
"core-select #damageTypeDropdown": function(event) {
"core-select .damageTypeDropdown": function(event) {
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var value = detail.item.getAttribute("name");
if (value == this.damageType) return;
Attacks.update(this._id, {$set: {damageType: value}});
},
"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({
@@ -41,5 +46,5 @@ Template.attackEdit.helpers({
},
DAMAGE_DICE: function() {
return DAMAGE_DICE;
}
},
});

View File

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

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">
{{#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>

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,5 +1,17 @@
<template name="shareDialog">
<div style="width: 360px;">
<div layout horizontal center>
<div>Who can view this character: </div>
<paper-dropdown-menu class="visibilityDropdown"
label="Visibility">
<paper-dropdown layered class="dropdown">
<core-menu class="menu visibilityMenu" selected={{viewPermission}}>
<paper-item name="whitelist">Only people I share with</paper-item>
<paper-item name="public">Anyone with link</paper-item>
</core-menu>
</paper-dropdown>
</paper-dropdown-menu>
</div>
<div>
{{#if readers.count}}
<div style="font-weight: 500;">
@@ -7,7 +19,7 @@
</div>
{{#each readers}}
<div layout horizontal center>
<div flex>{{username}}</div>
<div flex>{{getUserName}}</div>
<paper-icon-button class="deleteShare" icon="delete"></paper-icon-button>
</div>
{{/each}}

View File

@@ -3,6 +3,10 @@ Template.shareDialog.onCreated(function(){
});
Template.shareDialog.helpers({
viewPermission: function() {
var char = Characters.findOne(this._id, {fields: {settings: 1}});
return char.settings.viewPermission || "whitelist";
},
readers: function(){
var char = Characters.findOne(this._id, {fields: {readers: 1}});
return Meteor.users.find({_id: {$in: char.readers}});
@@ -19,9 +23,20 @@ Template.shareDialog.helpers({
return "User not found";
}
},
getUserName: function() {
return this.username || "user: " + this._id;
}
});
Template.shareDialog.events({
"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;

View File

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

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");
};
//watch this character and make sure their encumbrance is updated
trackEncumbranceConditions(this.data._id, this);
});
var setTab = function(charId, tab){
return Session.set(charId + ".selectedTab", tab);
@@ -13,7 +16,11 @@ var getTab = function(charId){
Template.characterSheet.helpers({
selectedTab: function(){
return getTab(this._id);
}
},
hideSpellcasting: function() {
var char = Characters.findOne(this._id);
return char && char.settings.hideSpellcasting;
},
});
Template.characterSheet.events({
@@ -40,4 +47,11 @@ Template.characterSheet.events({
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 {
width: 120px;
html /deep/ .operationDropDown {
width: 152px;
}
body /deep/ #statDropDown {
width: 120px;
html /deep/ .statDropDown {
width: 152px;
}
body /deep/ #operationDropDown {
width: 100px;
}
body /deep/ #damageMultiplierDropDown {
width: 120px;
}
body /deep/ #proficiencyDropDown {
width: 120px;
html /deep/ .damageMultiplierDropDown {
width: 152px;
}
html /deep/ .effectEdit paper-input {
@@ -24,6 +16,7 @@ html /deep/ .effectEdit paper-input {
}
html /deep/ .effectEdit {
height: 64px;
display: flex;
align-items: flex-end;
}

View File

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

View File

@@ -42,6 +42,7 @@ var stats = [
{stat: "rageDamage", name: "Rage Damage", group: "Stats"},
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
{stat: "level3SpellSlots", name: "level 3", group: "Spell Slots"},
@@ -93,24 +94,17 @@ var skillOperations = [
{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({
selectedStatGroup: function(){
return Template.instance().selectedStatGroup.get();
},
statGroups: function(){
return statGroupNames;
},
stats: function(){
var group = Template.instance().selectedStatGroup.get();
var group = this;
return statGroups[group];
},
operations: function(){
var group = Template.instance().selectedStatGroup.get();
var stat = statsDict[this.stat];
var group = stat && stat.group;
if (group === "Weakness/Resistance") return null;
if (group === "Saving Throws" || group === "Skills"){
return skillOperations;
@@ -120,7 +114,8 @@ Template.effectEdit.helpers({
},
effectValueTemplate: function(){
//resistance/vulnerability template
var group = Template.instance().selectedStatGroup.get();
var stat = statsDict[this.stat];
var group = stat && stat.group;
if (group === "Weakness/Resistance") return "multiplierEffectValue";
var op = this.operation;
@@ -144,25 +139,6 @@ Template.effectEdit.events({
Effects.softRemoveNode(this._id);
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 === "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){
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;

View File

@@ -42,6 +42,7 @@ var stats = {
"rageDamage":{"name":"Rage Damage"},
"expertiseDice":{"name":"Expertise Dice"},
"superiorityDice":{"name":"Superiority Dice"},
"carryMultiplier": {"name": "Carry Capacity Multiplier"},
"level1SpellSlots":{"name":"level 1 Spell Slots"},
"level2SpellSlots":{"name":"level 2 Spell Slots"},
"level3SpellSlots":{"name":"level 3 Spell Slots"},
@@ -57,40 +58,40 @@ var stats = {
"d12HitDice":{"name":"d12 Hit Dice"},
"acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
"bludgeoningMultiplier":{
"name":"Bludgeoning damage", "group": "Weakness/Resistance"
"name":"Bludgeoning damage", "group": "Weakness/Resistance",
},
"coldMultiplier":{
"name":"Cold damage", "group": "Weakness/Resistance"
"name":"Cold damage", "group": "Weakness/Resistance",
},
"fireMultiplier":{
"name":"Fire damage", "group": "Weakness/Resistance"
"name":"Fire damage", "group": "Weakness/Resistance",
},
"forceMultiplier":{
"name":"Force damage", "group": "Weakness/Resistance"
"name":"Force damage", "group": "Weakness/Resistance",
},
"lightningMultiplier":{
"name":"Lightning damage", "group": "Weakness/Resistance"
"name":"Lightning damage", "group": "Weakness/Resistance",
},
"necroticMultiplier":{
"name":"Necrotic damage", "group": "Weakness/Resistance"
"name":"Necrotic damage", "group": "Weakness/Resistance",
},
"piercingMultiplier":{
"name":"Piercing damage", "group": "Weakness/Resistance"
"name":"Piercing damage", "group": "Weakness/Resistance",
},
"poisonMultiplier":{
"name":"Poison damage", "group": "Weakness/Resistance"
"name":"Poison damage", "group": "Weakness/Resistance",
},
"psychicMultiplier":{
"name":"Psychic damage", "group": "Weakness/Resistance"
"name":"Psychic damage", "group": "Weakness/Resistance",
},
"radiantMultiplier":{
"name":"Radiant damage", "group": "Weakness/Resistance"
"name":"Radiant damage", "group": "Weakness/Resistance",
},
"slashingMultiplier":{
"name":"Slashing damage", "group": "Weakness/Resistance"
"name":"Slashing damage", "group": "Weakness/Resistance",
},
"thunderMultiplier":{
"name":"Thunder damage", "group": "Weakness/Resistance"
"name":"Thunder damage", "group": "Weakness/Resistance",
},
};
@@ -98,7 +99,7 @@ var operations = {
base: {name: "Base Value: "},
proficiency: {name: "Proficiency"},
add: {name: "+"},
mul: {name: "x"},
mul: {name: "×"},
min: {name: "Min: "},
max: {name: "Max: "},
advantage: {name: "Advantage"},
@@ -110,8 +111,8 @@ var operations = {
Template.effectView.helpers({
sourceName: function(){
var id = this.parent.id;
if(!id) return;
switch(this.parent.collection){
if (!id) return;
switch (this.parent.collection){
case "Features":
return "Feature - " + Features.findOne(id, {fields: {name: 1}}).name;
case "Classes":
@@ -130,33 +131,39 @@ Template.effectView.helpers({
return stats[this.stat] && stats[this.stat].name || "No Stat";
},
operationName: function(){
if(this.operation === "proficiency" ||
if (this.operation === "proficiency" ||
this.operation === "conditional") return null;
if(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";
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(){
if(this.operation === "advantage" ||
if (this.operation === "advantage" ||
this.operation === "disadvantage" ||
this.operation === "fail"){
return null;
}
if(this.operation === "proficiency"){
if(this.value == 0.5 || this.calculation == 0.5) return "Half Proficiency";
if(this.value == 1 || this.calculation == 1) return "Proficiency";
if(this.value == 2 || this.calculation == 2) return "Double Proficiency";
if (this.operation === "proficiency"){
if (this.value == 0.5 || this.calculation == 0.5)
return "Half Proficiency";
if (this.value == 1 || this.calculation == 1)
return "Proficiency";
if (this.value == 2 || this.calculation == 2)
return "Double Proficiency";
}
if(this.operation === "conditional"){
if (this.operation === "conditional"){
return this.calculation || this.value;
}
if(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";
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;
if (_.isNumber(value)) return value;
return this.calculation || this.value;
}
},
});

View File

@@ -32,7 +32,7 @@
{{/if}}
{{#if description}}
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}

View File

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

View File

@@ -3,14 +3,19 @@ Template.features.helpers({
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
return features;
},
shortDescription: function() {
if (_.isString(this.description)){
return this.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0];
}
},
hasUses: function(){
return this.usesValue() > 0;
},
noUsesLeft: function(){
return this.usesLeft() <= 0;
return this.usesLeft() <= 0 || !canEditCharacter(this.charId);
},
usesFull: function(){
return this.usesLeft() >= this.usesValue();
return this.usesLeft() >= this.usesValue() || !canEditCharacter(this.charId);
},
colorClass: function(){
return getColorClass(this.color);
@@ -65,7 +70,7 @@ Template.features.events({
}
});
},
"tap .featureCard .containerTop": function(event){
"tap .featureCard .top": function(event){
var featureId = this._id;
var charId = Template.parentData()._id;
GlobalUI.setDetail({
@@ -96,16 +101,19 @@ Template.features.events({
Template.resource.helpers({
cantIncrement: function(){
var baseBigger = this.char.attributeValue(this.name) <
this.char.attributeBase(this.name);
return !baseBigger;
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(){
var valuePositive = this.char.attributeValue(this.name) > 0;
return !valuePositive;
var value = Characters.calculate.attributeValue(this.char._id, this.name);
var valuePositive = value > 0;
return !valuePositive || !canEditCharacter(this.char._id);
},
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;
} else {
return "grey";
@@ -115,20 +123,23 @@ Template.resource.helpers({
Template.resource.events({
"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: {}};
modifier.$inc[this.name + ".adjustment"] = 1;
Characters.update(this.char._id, modifier, {validate: false});
}
},
"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: {}};
modifier.$inc[this.name + ".adjustment"] = -1;
Characters.update(this.char._id, modifier, {validate: false});
}
},
"tap .containerRight": function(event, instance) {
"tap .right": function(event, instance) {
GlobalUI.setDetail({
template: "attributeDialog",
data: {name: this.title, statName: this.name, charId: this.char._id},

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,67 @@
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 carryMultiplier = Characters.calculate
.attributeValue(char._id, "carryMultiplier");
var capacity = strength * 15 * carryMultiplier;
return weight / capacity;
};
Template.carryCapacityBar.onCreated(function() {
var self = this;
self.carriedFraction = new ReactiveVar(0);
self.autorun(function() {
self.carriedFraction.set(getFractionCarried(Template.currentData()));
});
});
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

@@ -34,13 +34,13 @@
<template name="containerView">
<div layout horizontal wrap center justified>
<table class="summaryTable fullwidth">
<tr><td>Container</td><td>{{weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
<tr><td>Container</td><td>{{round weight}}lbs</td><td>{{longValueString value}}</td></tr>
<tr><td>Contents</td><td>{{round contentsWeight}}lbs</td><td>{{longValueString contentsValue}}</td></tr>
<tr class="body2"><td>Total</td><td>{{round totalWeight}}lbs</td><td>{{longValueString totalValue}}</td></tr>
</table>
</div>
{{#if description}}
<hr class="vertMargin">
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{/if}}
</template>

View File

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

View File

@@ -44,6 +44,12 @@ Template.inventory.helpers({
).forEach(function(item){
worth += item.totalValue();
});
Containers.find(
{charId: this._id},
{fields: {value : 1}}
).forEach(function(container) {
if (container.value) worth += container.value;
});
return worth;
},
weightCarried: function(){
@@ -61,6 +67,18 @@ Template.inventory.helpers({
});
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(){
var value = 0;
Items.find(
@@ -103,7 +121,7 @@ Template.inventory.helpers({
});
Template.inventory.events({
"tap #addItem": function(event){
"tap .addItem": function(event){
var charId = this._id;
Items.insert({
charId: charId,
@@ -120,7 +138,7 @@ Template.inventory.events({
});
});
},
"tap #addContainer": function(event){
"tap .addContainer": function(event){
var containerId = Containers.insert({
name: "New Container",
isCarried: true,
@@ -136,6 +154,23 @@ Template.inventory.events({
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){
var itemId = this._id;
var charId = Template.parentData()._id;
@@ -145,7 +180,32 @@ Template.inventory.events({
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({
template: "containerDialog",
data: {containerId: this._id, charId: this.charId},
@@ -167,6 +227,9 @@ Template.inventoryItem.helpers({
ne1: function(num){
return num !== 1;
},
lt1: function(num) {
return num < 1;
},
hidden: function(){
return Session.equals("inventory.dragItemId", this._id) ? "hidden" : null;
},
@@ -174,71 +237,93 @@ Template.inventoryItem.helpers({
Template.layout.events({
"dragstart .inventoryItem": function(event, instance){
event.originalEvent.dataTransfer.setData("dicecloud-id/items", 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){
resetInvetorySession(); //this is a valid drop zone
Session.set("inventory.dragItemId", null);
},
"dragover .itemContainer": function(event, instance){
event.preventDefault();
},
"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"));
"drop .itemContainer": 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: item._id,
id: itemId,
parentCollection: "Containers",
parentId: this._id,
},
});
} else {
//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){
var charId = Session.get("inventory.dragItemOriginalCharacter");
var item = Items.findOne(Session.get("inventory.dragItemId"));
item.equip(charId);
resetInvetorySession();
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
Meteor.call("equipItem", itemId, this._id);
Session.set("inventory.dragItemId", null);
},
"drop .carriedContainer": function(event, instance){
var charId = Session.get("inventory.dragItemOriginalCharacter");
var item = Items.findOne(Session.get("inventory.dragItemId"));
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
GlobalUI.showDialog({
template: "splitStackDialog",
data: {
id: item._id,
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
//move item to the character
item.moveToCharacter(this._id);
Meteor.call("moveItemToCharacter", itemId, this._id);
}
resetInvetorySession();
Session.set("inventory.dragItemId", 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);
},
});
var resetInvetorySession = function(){
_.defer(function(){
Session.set("inventory.dragItemId", null);
Session.set("inventory.dragItemOriginalContainer", null);
Session.set("inventory.dragItemOriginalCharacter", null);
});
};

View File

@@ -10,7 +10,7 @@
<template name="itemDetails">
<div layout horizontal wrap center justified class="headline">
{{#if weight}}<div class="sideMargin">{{totalWeight}}lbs</div>{{/if}}
{{#if weight}}<div class="sideMargin">{{round totalWeight}}lbs</div>{{/if}}
{{#if value}}<div>{{valueString totalValue}}</div>{{/if}}
</div>
<div layout horizontal wrap class="caption">
@@ -18,8 +18,8 @@
{{#if requiresAttunement}}<div class="vertMargin">Requires Attunement</div>{{/if}}
</div>
{{#if description}}
<hr class="vertMargin">
<div class="prewrap">{{description}}</div>
<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}}
@@ -28,10 +28,25 @@
<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>
<input id="quantityInput" type="number" value={{quantity}}>
<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}}
{{# 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">
@@ -53,7 +68,9 @@
</div>
<div center horizontal layout>
<div class="padded">Requires Attunement</div>
<paper-checkbox id="attunementCheckbox" checked={{requiresAttunement}}></paper-checkbox>
<paper-checkbox id="attunementCheckbox"
checked={{requiresAttunement}}>
</paper-checkbox>
</div>
</div>

View File

@@ -50,7 +50,7 @@ Template.itemEdit.onRendered(function(){
Template.itemEdit.helpers({
ne1: function(num){
return num != 1;
}
},
});
Template.itemEdit.events({
@@ -81,15 +81,16 @@ Template.itemEdit.events({
},
"change #equippedInput": function(event){
var equipped = Template.instance().find("#equippedInput").checked;
var item = Items.findOne(this._id);
if (item){
if (equipped){
item.equip();
} else {
item.unequip();
}
if (equipped){
Meteor.call("equipItem", this._id, this.charId);
} else {
Meteor.call("unequipItem", this._id, this.charId);
}
},
"change #incrementCheckbox": function(event){
var value = event.currentTarget.checked;
Items.update(this._id, {$set: {"settings.showIncrement": value}});
},
"change #attunementCheckbox": function(event){
var value = event.currentTarget.checked;
Items.update(this._id, {$set: {requiresAttunement: value}});
@@ -107,7 +108,6 @@ Template.containerDropdown.events({
var detail = event.originalEvent.detail;
if (!detail.isSelected) return;
var containerId = detail.item.getAttribute("name");
var item = Items.findOne(Template.currentData()._id);
item.moveToContainer(containerId);
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

@@ -7,13 +7,12 @@ Template.splitStackDialog.helpers({
Template.splitStackDialog.events({
"tap #moveButton": function(event, instance){
var item = Items.findOne(this.id);
if (item){
item.splitToParent(
{collection: this.parentCollection , id: this.parentId},
+instance.find("#quantityInput").value
);
}
Meteor.call(
"splitItemToParent",
this.id,
+instance.find("#quantityInput").value,
{collection: this.parentCollection , id: this.parentId}
);
},
"tap #oneButton":function(event, instance){
instance.find("#quantityInput").value = 1;

View File

@@ -3,6 +3,9 @@ Template.classDialog.onRendered(function(){
});
Template.classDialog.events({
"color-change": function(event, instance){
Classes.update(instance.data.classId, {$set: {color: event.color}});
},
"tap #deleteButton": function(event, instance){
Classes.softRemoveNode(instance.data.classId);
GlobalUI.deletedToast(instance.data.classId, "Classes", "Class");

View File

@@ -1,28 +1,32 @@
<template name="experienceDialog">
{{#with experience}}
{{#baseDialog title=name class=colorClass hideColor="true" startEditing=../startEditing}}
<div horizontal layout center-justified>
{{#baseDialog title=name class=color hideColor="true" startEditing=../startEditing}}
<div horizontal layout center-justified class= "display2">
{{value}}
</div>
{{#if description}}
<hr class="vertMargin">
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{description}}{{/markdown}}</div>
{{/if}}
{{else}}
<div horizontal layout>
<!--Name-->
<paper-input id="experienceNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
<!--Value-->
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
</div>
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="experienceDescriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
{{> experienceEdit}}
{{/baseDialog}}
{{/with}}
</template>
</template>
<template name="experienceEdit">
<div horizontal layout>
<!--Name-->
<paper-input id="experienceNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
<!--Value-->
<paper-input-decorator label="Value" floatinglabel>
<input id="valueInput" type="number" value={{value}}>
</paper-input-decorator>
</div>
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="experienceDescriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
</template>

View File

@@ -1,6 +1,15 @@
Template.experienceEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.experienceDialog.helpers({
feature: function(){
return Features.findOne(this.featureId);
experience: function(){
Experiences.findOne(this.experienceId);
return Experiences.findOne(this.experienceId);
},
color: function() {
var char = Characters.findOne(this.charId, {fields: {color: 1}});
if (char) return getColorClass(char.color);
},
});
@@ -13,8 +22,10 @@ Template.experienceDialog.events({
);
GlobalUI.closeDetail();
},
//TODO validate input (integer, non-negative, etc) for these inputs and give validation errors
"change #experienceNameInput, input #experienceNameInput": function(event){
});
Template.experienceEdit.events({
"change #experienceNameInput": function(event){
var value = event.currentTarget.value;
Experiences.update(this._id, {$set: {name: value}});
},
@@ -27,10 +38,3 @@ Template.experienceDialog.events({
Experiences.update(this._id, {$set: {description: value}});
},
});
Template.experienceDialog.helpers({
experience: function(){
Experiences.findOne(this.experienceId);
return Experiences.findOne(this.experienceId);
}
});

View File

@@ -1,78 +1,108 @@
<template name="journal">
<div fit>
<div id="journal" class="scroll-y" fit>
<div class="containers">
<div class="column-container">
<!--Experience Table-->
<paper-shadow class="card container experiencesCard" hero-id="main" {{detailHero}}>
<div class="whiteTop" hero-id="toolbar" layout horizontal center {{detailHero}}>
<div class="containerName subhead" flex>Experience</div>
<div class="subhead">{{experience}} XP</div>
<paper-icon-button class="black54" id="addXP" icon="add"></paper-icon-button>
<div><paper-shadow class="card experiencesCard"
hero-id="main" {{detailHero}}>
<div class="top white subhead"
hero-id="toolbar" {{detailHero}}
layout horizontal center>
<div flex>Experience</div>
<div >{{characterCalculate "experience" _id}} XP</div>
<paper-icon-button class="black54" id="addXP" icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}></paper-icon-button>
</div>
<div class="containerMain experiences">
<div class="bottom list">
{{#each experiences}}
<div class="itemSlot">
<paper-item class="inventoryItem experience" hero-id="main" {{detailHero}} layout horizontal>
<div flex>{{name}}</div><div class="xpValue">{{value}}</div>
</paper-item>
<div class="item-slot">
<div class="item experience"
hero-id="main" {{detailHero}}
layout horizontal center>
<div flex>{{name}}</div>
<div class="xpValue">{{value}}</div>
</div>
</div>
{{/each}}
</div>
{{#if moreExperiencesOrCollapse}}
<div class="containerFoot" layout="" horizontal="" center="" end-justified="">
<paper-button id="moreExperiences" disabled={{notMoreExperiences}}>Load More</paper-button>
<paper-button id="lessExperiences" disabled={{cantCollapse}}>Collapse</paper-button>
<div layout horizontal center end-justified>
<paper-button id="moreExperiences"
disabled={{notMoreExperiences}}>
Load More
</paper-button>
<paper-button id="lessExperiences"
disabled={{cantCollapse}}>
Collapse
</paper-button>
</div>
{{/if}}
</paper-shadow>
</paper-shadow></div>
<!--Class Table-->
<paper-shadow class="card container" hero-id="main" {{detailHero}}>
<div class="whiteTop" hero-id="toolbar" layout horizontal center {{detailHero}}>
<div><paper-shadow class="card"
hero-id="main" {{detailHero}}>
<div class="white top"
hero-id="toolbar" {{detailHero}}
layout horizontal center>
<div flex>
<div class="containerName subhead">Level {{level}}</div>
<div class="containerName subhead">
Level {{characterCalculate "level" _id}}
</div>
{{#if nextLevelXP}}
<div class="caption">
Next Level: {{nextLevelXP}}XP
</div>
{{/if}}
</div>
<paper-icon-button class="black54" id="addClassButton" icon="add"></paper-icon-button>
<paper-icon-button class="black54"
id="addClassButton"
icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
</paper-icon-button>
</div>
<div class="containerMain experiences">
<div class="itemSlot">
<paper-item class="inventoryItem race" hero-id="main" {{detailHero "race" _id}} layout horizontal>
<div class="bottom list">
<div class="item-slot">
<div class="item race"
hero-id="main" {{detailHero "race" _id}}
layout horizontal center>
{{race}}
</paper-item>
</div>
</div>
{{#each classes}}
<div class="itemSlot">
<paper-item class="inventoryItem class" hero-id="main" {{detailHero}} layout horizontal>
<div class="item-slot">
<div class="item class"
hero-id="main" {{detailHero}}
layout horizontal center>
{{name}}&nbsp;{{level}}
</paper-item>
</div>
</div>
{{/each}}
</div>
</paper-shadow>
</paper-shadow></div>
<!--Notes-->
{{#each notes}}
<paper-shadow class="card container" hero-id="main" {{detailHero}}>
<div class="containerTop {{colorClass}} noteTop" hero-id="toolbar" layout horizontal center {{detailHero}}>
<div flex>
<div class="containerName subhead">{{name}}</div>
</div>
<div>
<paper-shadow class="card" hero-id="main" {{detailHero}}>
<div class="top {{colorClass}} noteTop subhead"
hero-id="toolbar" {{detailHero}}
layout horizontal center>
{{name}}
</div>
<div class="containerMain preline">{{description}}</div>
<div class="bottom">{{#markdown}}{{description}}{{/markdown}}</div>
</paper-shadow>
</div>
{{/each}}
</div>
<div class="fab-buffer"></div>
</div>
</div>
<paper-fab id="addNote"
class="floatyButton"
icon="add"
title="Add"
role="button"
tabindex="0"
{{#if canEditCharacter _id}}
<paper-fab id="addNote"
class="floatyButton"
icon="add"
title="Add"
role="button"
tabindex="0"
hero-id="main"></paper-fab>
</template>
{{/if}}
</template>

View File

@@ -41,7 +41,7 @@ Template.journal.helpers({
return Levels.find({charId: charId, classId: this._id}, {sort: {value: 1}});
},
nextLevelXP: function(){
var currentLevel = this.level();
var currentLevel = Characters.calculate.level(this._id);
if (currentLevel < 20){
return XP_TABLE[currentLevel];
}

View File

@@ -1,18 +1,30 @@
<template name="noteDialog">
{{#with note}}
{{#baseDialog title=name class=colorClass startEditing=../startEditing}}
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{description}}{{/markdown}}</div>
{{else}}
<!--Name-->
<div horizontal layout>
<paper-input id="noteNameInput" label="Name" floatinglabel value={{name}} flex></paper-input>
</div>
<!--Description-->
<paper-input-decorator label="Description" floatinglabel layout vertical>
<paper-autogrow-textarea>
<textarea id="noteDescriptionInput" value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
{{> noteDialogEdit}}
{{/baseDialog}}
{{/with}}
</template>
<template name="noteDialogEdit">
<!--Name-->
<div horizontal layout>
<paper-input id="noteNameInput"
label="Name"
floatinglabel
value={{name}}
flex>
</paper-input>
</div>
<!--Description, formatting this nicely breaks it, leave it as is-->
<paper-input-decorator label="Description"
floatinglabel
layout vertical>
<paper-autogrow-textarea>
<textarea id="noteDescriptionInput"
value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
</template>

View File

@@ -1,5 +1,7 @@
Template.noteDialog.onRendered(function(){
updatePolymerInputs(this);
Template.noteDialog.helpers({
note: function(){
return Notes.findOne(this.noteId);
}
});
Template.noteDialog.events({
@@ -11,6 +13,13 @@ Template.noteDialog.events({
GlobalUI.deletedToast(instance.data.noteId, "Notes", "Note");
GlobalUI.closeDetail();
},
});
Template.noteDialogEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.noteDialogEdit.events({
"change #noteNameInput, input #noteNameInput": function(event){
var value = event.currentTarget.value;
Notes.update(this._id, {$set: {name: value}});
@@ -20,9 +29,3 @@ Template.noteDialog.events({
Notes.update(this._id, {$set: {description: value}});
},
});
Template.noteDialog.helpers({
note: function(){
return Notes.findOne(this.noteId);
}
});

View File

@@ -1,5 +1,8 @@
<template name="raceDialog">
{{#baseDialog title="Race" class=colorClass hideColor="true" hideDelete="true" startEditing=startEditing}}
{{#baseDialog title="Race" class=color hideColor="true" hideDelete="true" startEditing=startEditing}}
<div horizontal layout center-justified class= "display2">
{{race}}
</div>
{{> effectsViewList charId=charId parentId=charId parentGroup="racial"}}
{{> proficiencyViewList charId=charId parentId=charId parentGroup="racial"}}
{{else}}

View File

@@ -13,5 +13,9 @@ Template.raceDialog.helpers({
race: function(){
var char = Characters.findOne(this.charId, {fields: {race: 1}});
return char && char.race;
}
},
color: function() {
var char = Characters.findOne(this.charId, {fields: {color: 1}});
if (char) return getColorClass(char.color);
},
});

View File

@@ -0,0 +1,9 @@
<template name="backgroundDialog">
{{#baseDialog title=title class=colorClass hideColor="true" hideDelete="true"}}
<div>{{#markdown}}{{evaluateString charId value}}{{/markdown}}</div>
{{> proficiencyViewList charId=charId parentId=charId parentGroup="background"}}
{{else}}
{{> textDialogEdit}}
{{> proficiencyEditList parentId=charId parentCollection="Characters" charId=charId parentGroup="background"}}
{{/baseDialog}}
</template>

View File

@@ -0,0 +1,8 @@
Template.backgroundDialog.helpers({
value: function(){
var fieldSelector = {fields: {}};
fieldSelector.fields[this.field] = 1;
var char = Characters.findOne(this.charId, fieldSelector);
return char[this.field];
}
});

View File

@@ -1,18 +1,36 @@
<template name="personaDetailsDialog">
{{#baseDialog title=name class="deep-purple white-text" hideColor="true" hideDelete="true" startEditing=startEditing}}
{{alignment}} {{gender}} {{race}}
{{#with char}}
<div>{{alignment}} {{gender}} {{race}}</div>
<core-image style="width: 350px; height: 350px; margin-top: 8px;"
sizing="cover"
hero-id="image" hero
src={{picture}}></core-image>
{{/with}}
{{else}}
{{> personaDetailsEdit}}
{{#with char}}
{{> personaDetailsEdit}}
{{/with}}
{{/baseDialog}}
</template>
<template name="personaDetailsEdit">
<!--Name-->
<paper-input id="nameInput" label="Name" floatinglabel value={{name}}></paper-input><br>
<!--Alignment-->
<paper-input id="alignmentInput" label="Alignment" floatinglabel value={{alignment}}></paper-input><br>
<!--Gender-->
<paper-input id="genderInput" label="Gender" floatinglabel value={{gender}}></paper-input><br>
<!--Race-->
<paper-input id="raceInput" label="Race" floatinglabel value={{race}}></paper-input><br>
<div layout horizontal center-justified>
<div flex style="max-width: 350px;" layout vertical>
<!--Name-->
<paper-input id="nameInput" label="Name" floatinglabel value={{name}}></paper-input>
<!--Alignment-->
<paper-input id="alignmentInput" label="Alignment" floatinglabel value={{alignment}}></paper-input>
<!--Gender-->
<paper-input id="genderInput" label="Gender" floatinglabel value={{gender}}></paper-input>
<!--Race-->
<paper-input id="raceInput" label="Race" floatinglabel value={{race}}></paper-input>
<!--Picture-->
<paper-input id="pictureInput" label="Picture URL" floatinglabel value={{picture}}></paper-input>
<core-image style="height:350px; width: 100%; margin-top: 8px;"
sizing="cover"
hero-id="image" hero
src={{picture}}></core-image>
</div>
</div>
</template>

View File

@@ -2,21 +2,34 @@ Template.personaDetailsEdit.onRendered(function(){
updatePolymerInputs(this);
});
Template.personaDetailsDialog.helpers({
char: function() {
return Characters.findOne(
this._id,
{fields: {name: 1, alignment: 1, gender: 1, race: 1, picture: 1}}
);
}
});
Template.personaDetailsEdit.events({
"change #nameInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {name: input}});
Characters.update(this._id, {$set: {name: input}});
},
"change #alignmentInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {alignment: input}});
Characters.update(this._id, {$set: {alignment: input}});
},
"change #genderInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {gender: input}});
Characters.update(this._id, {$set: {gender: input}});
},
"change #raceInput": function(event){
var input = event.currentTarget.value;
Characters.update(this.charId, {$set: {race: input}});
Characters.update(this._id, {$set: {race: input}});
},
"change #pictureInput": function(event){
var input = event.currentTarget.value;
Characters.update(this._id, {$set: {picture: input}});
},
});

View File

@@ -1,40 +1,77 @@
<template name="persona">
<div fit>
<div id="persona" class="scroll-y" fit>
<div class="containers">
<div class="column-container">
{{#with characterDetails}}
{{#containerCardHelper this}}{{alignment}} {{gender}} {{race}}{{/containerCardHelper}}
<div>
<paper-shadow class="card"
hero-id="main" {{detailHero "details" _id}}>
{{#unless picture}}
<div class="top subhead characterField {{colorClass}}"
hero-id="toolbar" {{detailHero "details" _id}}>
<div class="subhead" flex
hero-id="title" {{detailHero "details" _id}}>
{{name}}
</div>
</div>
{{else}}
<core-image class="characterField clickable"
style="height:350px; width: 100%;
background-color: #e8e8e8;"
sizing="cover"
hero-id="image" {{detailHero "details" _id}}
src={{picture}}></core-image>
{{/unless}}
<div class="bottom">
{{#if picture}}
<div class="title" hero-id="title" {{detailHero "details" _id}}>
{{name}}
</div>
{{/if}}
<div class="subhead">
{{alignment}} {{gender}} {{race}}
</div>
</div>
</paper-shadow>
</div>
{{/with}}
{{> containerCard characterField "description" "Description"}}
{{> containerCard characterField "personality" "Personality Traits"}}
{{> containerCard characterField "ideals" "Ideals"}}
{{> containerCard characterField "bonds" "Bonds"}}
{{> containerCard characterField "flaws" "Flaws"}}
{{> containerCard characterField "backstory" "Background"}}
<paper-shadow class="card container">
<div class="containerTop whiteTop" layout horizontal center>
<div class="containerName subhead" flex>Languages</div>
<div>{{> containerCard characterField "description" "Description"}}</div>
<div>{{> containerCard characterField "personality" "Personality Traits"}}</div>
<div>{{> containerCard characterField "ideals" "Ideals"}}</div>
<div>{{> containerCard characterField "bonds" "Bonds"}}</div>
<div>{{> containerCard characterField "flaws" "Flaws"}}</div>
<div>{{> containerCard characterField "backstory" "Background"}}</div>
<div>
<paper-shadow class="card">
<div class="white top subhead">
Languages
</div>
<div flex class="containerMain listPadded">
<div class="bottom list">
{{#each languages}}
{{> proficiencyListItem}}
{{/each}}
</div>
</paper-shadow>
</div>
</div>
</div>
</div>
</template>
<template name="containerCard">
{{#containerCardHelper this}}{{body}}{{/containerCardHelper}}
{{#containerCardHelper this}}{{evaluateString _id body}}{{/containerCardHelper}}
</template>
<template name="containerCardHelper">
<paper-shadow class="card container {{class}}" hero-id="main" {{detailHero field ../_id}}>
<div class="containerTop {{colorClass}} {{topClass}}" hero-id="toolbar" layout horizontal center {{detailHero field ../_id}}>
<div class="containerName subhead" hero-id="title" flex {{detailHero field ../_id}}>{{title}}</div>
<paper-shadow class="card {{class}}"
hero-id="main" {{detailHero field ../_id}}>
<div class="top subhead {{colorClass}} {{topClass}}"
hero-id="toolbar" {{detailHero field ../_id}}>
<div class="subhead" flex
hero-id="title" {{detailHero field ../_id}}>
{{title}}
</div>
</div>
<div flex class="containerMain prewrap">{{> UI.contentBlock}}</div>
<div class="bottom">{{#markdown}}{{> UI.contentBlock}}{{/markdown}}</div>
</paper-shadow>
</template>
</template>

View File

@@ -11,12 +11,12 @@ Template.persona.helpers({
characterDetails: function(){
var char = Characters.findOne(
this._id,
{fields: {name: 1, gender: 1, alignment: 1, race:1}}
{fields: {name: 1, gender: 1, alignment: 1, race:1, picture: 1}}
);
char.field = "details";
char.title = char.name;
char.color = "d";
char.topClass = "characterField";
char.startEditing = true;
return char;
},
characterField: function(field, title){
@@ -40,25 +40,28 @@ Template.persona.helpers({
Template.persona.events({
"tap .characterField": function(event){
if (this.field !== "details"){
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "textDialog",
data: {
charId: charId,
field: this.field,
title: this.title,
color: this.color,
},
heroId: this._id + this.field,
});
} else {
if (this.field == "details"){
this.charId = Template.parentData()._id;
GlobalUI.setDetail({
template: "personaDetailsDialog",
data: this,
heroId: this._id + this.field,
});
} else {
var template = "textDialog";
if (this.field === "backstory") template = "backgroundDialog";
var charId = Template.parentData()._id;
GlobalUI.setDetail({
template: template,
data: {
charId: charId,
field: this.field,
title: this.title,
color: this.color,
startEditing: true,
},
heroId: this._id + this.field,
});
}
}
});

View File

@@ -1,6 +1,6 @@
<template name="textDialog">
{{#baseDialog title=title class=colorClass hideColor="true" hideDelete="true" startEditing=startEditing}}
<div class="prewrap">{{value}}</div>
<div>{{#markdown}}{{evaluateString charId value}}{{/markdown}}</div>
{{else}}
{{> textDialogEdit}}
{{/baseDialog}}

View File

@@ -1,8 +1,11 @@
<template name="proficiencyListItem">
<div class="itemSlot">
<paper-item noink class="white proficiencyItem" hero-id="main" {{detailHero}}>
<core-icon icon="{{profIcon}}" class="black54"></core-icon>
<div class="sideMargin">{{getName}}</div>
</paper-item>
<div class="item-slot">
<div class="proficiency item small"
hero-id="main" {{detailHero}}
layout horizontal center>
<core-icon icon="{{profIcon}}"
style="margin-right: 16px;"></core-icon>
<div flex>{{getName}}</div>
</div>
</div>
</template>

View File

@@ -14,7 +14,7 @@ Template.proficiencyListItem.helpers({
});
Template.proficiencyListItem.events({
"tap .proficiencyItem": function(event, instance){
"tap .proficiency": function(event, instance){
openParentDialog(this.parent, this.charId, this._id);
}
});

View File

@@ -1,6 +1,10 @@
<template name="proficiencyView">
<div class="proficiencyView" layout horizontal center>
<core-icon icon="{{profIcon}}"></core-icon>
<div class="sideMargin">{{getName}}</div>
<div class="proficiencyView item small"
style="padding: 0;"
layout horizontal center>
<core-icon icon="{{profIcon}}" style="margin-right: 16px;"></core-icon>
<div>{{getName}}</div>
</div>
</template>

View File

@@ -2,7 +2,7 @@
{{#if proficiencies.count}}
<hr class="vertMargin">
<div class="proficiencies">
<h2 class="spaceAfter">Proficiencies</h2>
<h2 style="margin-bottom: 8px;">Proficiencies</h2>
{{#each proficiencies}}
{{> proficiencyView}}
{{/each}}

View File

@@ -9,24 +9,33 @@
</template>
<template name="spellDetails">
<div class="caption">
Level {{level}} {{school}}, {{preparedString}}
<div class="body2">
Level {{level}} {{school}} {{#if ritual}}ritual{{/if}}, {{preparedString}}
</div>
<div class="vertMargin">
<div style="margin: 16px 0 16px 0;">
{{#if castingTime}}
<div>
<span class="body2">Casting Time: </span><span>{{castingTime}}</span>
</div>
{{/if}}
{{#if range}}
<div>
<span class="body2">Range: </span><span>{{range}}</span>
</div>
{{/if}}
{{#if getComponents}}
<div>
<span class="body2">Components: </span><span>{{getComponents}}</span>
</div>
{{/if}}
{{#if duration}}
<div>
<span class="body2">Duration: </span><span>{{duration}}</span>
</div>
{{/if}}
</div>
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
{{> attacksViewList charId=charId parentId=_id}}
</template>
<template name="spellEdit">
@@ -126,4 +135,5 @@
<textarea id="descriptionInput" placeholder value={{description}}></textarea>
</paper-autogrow-textarea>
</paper-input-decorator>
</template>
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name}}
</template>

View File

@@ -20,7 +20,7 @@
{{/if}}
</div>
<hr class="vertMargin">
<div class="prewrap">{{description}}</div>
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
</div>
{{else}}
<!--Name-->

View File

@@ -1,84 +1,121 @@
<template name="spells">
<div fit>
<div id="spells" class="scroll-y" fit>
<div class="spellsContainer" layout horizontal start wrap>
<div style="padding: 4px;"
layout horizontal start wrap>
{{#if hasSlots}}
<paper-shadow class="card container spellSlotContainer" hero-id="main" {{detailHero}}>
<div class="containerTop whiteTop" layout horizontal center>
<div class="containerName subhead" hero-id="title" flex>Spell Slots</div>
<paper-shadow class="card"
style="margin: 4px;"
hero-id="main" {{detailHero}}>
<div class="white top subhead"
layout horizontal center>
Spell Slots
</div>
<div flex class="containerMain">
<div class="bottom list">
{{#each levels}}{{#if showSlots ..}}
<div class="itemSlot">
<paper-item class="inventoryItem spellSlot" hero-id="main" {{detailHero slotStatName ../_id}} layout horizontal>
<div class="slotName">
<div class="item-slot">
<div class="item spellSlot"
hero-id="main" {{detailHero slotStatName ../_id}}
layout horizontal center>
<div style="margin-right: 16px">
{{name}}
</div>
<div flex layout horizontal center>
{{#each slotBubbles ..}}
<paper-icon-button class="slotBubble" icon={{icon}} disabled={{disabled}}></paper-icon-button>
<paper-icon-button class="slotBubble"
icon={{icon}}
disabled={{disabled}}>
</paper-icon-button>
{{/each}}
</div>
</paper-item>
</div>
</div>
{{/if}}{{/each}}
</div>
</paper-shadow>
{{/if}}
{{#each spellLists}}
<paper-shadow class="card container spellList" hero-id="main" {{detailHero}} flex>
<div class="containerTop {{colorClass}}" hero-id="toolbar" layout horizontal center {{detailHero}}>
<paper-shadow class="card spellList" flex
hero-id="main" {{detailHero}}
style="margin: 4px;">
<div class="top {{colorClass}}"
hero-id="toolbar" {{detailHero}}
layout horizontal center>
<div flex>
<div class="containerName subhead">{{name}}</div>
<div class="subhead">{{name}}</div>
<div class="caption">
{{#if saveDC}}
Save DC: {{evaluate charId saveDC}}
<div style="width: 16px; display: inline-block;"></div>
<span style="margin-right: 16px;">
Save DC: {{evaluate charId saveDC}}
</span>
{{/if}}
{{#if attackBonus}}
Attack Bonus: {{evaluateSigned charId attackBonus}}
<span>
Attack Bonus: {{evaluateSigned charId attackBonus}}
</span>
{{/if}}
</div>
</div>
{{#if settings.showUnprepared}}
{{#if maxPrepared}}<div class="subhead">{{numPrepared}} / {{evaluate charId maxPrepared}}</div>{{/if}}
<core-tooltip label="Done" position="left">
<paper-icon-button class="finishPrep" icon="done"></paper-icon-button>
</core-tooltip>
{{#if maxPrepared}}
<div class="subhead">
{{numPrepared}} / {{evaluate charId maxPrepared}}
</div>
{{/if}}
<core-tooltip label="Done"
position="left">
<paper-icon-button class="finishPrep"
icon="done">
</paper-icon-button>
</core-tooltip>
{{else}}
<core-tooltip label="Change prepared spells" position="left">
<paper-icon-button class="prepSpells" icon="book"></paper-icon-button>
<core-tooltip label="Change prepared spells"
position="left">
<paper-icon-button class="prepSpells"
disabled={{#unless canEditCharacter charId}}true{{/unless}}
icon="book">
</paper-icon-button>
</core-tooltip>
{{/if}}
</div>
<div class="containerMain">
<div class="bottom list column-container">
{{#each levels}}
<div class="spellLevel">
{{#if spellCount .. ../../_id}}
<div class="list-subhead" layout horizontal center>
<div class="subhead">
{{name}}
</div>
{{/if}}
{{#each spells ../_id ../../_id}}
{{#if showSpell ../../settings.showUnprepared}}
<div class="itemSlot">
<paper-item class="inventoryItem spell" hero-id="main" {{detailHero}}
layout horizontal center>
<!--disabled={{cantCast ../level ../../..}} to grey out spells above highest usable slot-->
<core-icon icon="social:whatshot"
style="color: {{hexColor color}};"
<div class="item-slot">
<div class="tall spell item"
hero-id="main" {{detailHero}}
layout horizontal center>
<core-icon icon="social:whatshot"
style="color: {{hexColor color}};
margin-right: 16px;"
></core-icon>
<div flex layout vertical>
<div>{{name}}</div>
<div class="caption">
{{school}} {{castingTime}}
{{#if ritual}}(ritual){{/if}}{{#if spellComponents}} - {{spellComponents}}{{/if}}
{{school}}
{{castingTime}}
{{#if ritual}}
(ritual)
{{/if}}
{{#if spellComponents}}
- {{spellComponents}}
{{/if}}
</div>
</div>
{{#if ../../settings.showUnprepared}}
<paper-checkbox class="preparedCheckbox" checked={{isPrepared}} disabled={{cantUnprepare}}></paper-checkbox>
<paper-checkbox class="preparedCheckbox"
checked={{isPrepared}}
disabled={{cantUnprepare}}>
</paper-checkbox>
{{/if}}
</paper-item>
</div>
</div>
{{/if}}
{{/each}}
@@ -91,8 +128,20 @@
<div class="fab-buffer"></div>
</div>
</div>
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3">
<paper-fab-menu-item id="addSpell" icon="note-add" color="#d23f31" tooltip="Spell"></paper-fab-menu-item>
<paper-fab-menu-item id="addSpellList" icon="work" color="#d23f31" tooltip="Spell List"></paper-fab-menu-item>
</paper-fab-menu>
</template>
{{#if canEditCharacter _id}}
{{#fabMenu}}
<core-tooltip label="New spell list" position="left">
<paper-fab icon="work"
class="addSpellList"
mini>
</paper-fab>
</core-tooltip>
<core-tooltip label="New spell" position="left">
<paper-fab icon="note-add"
class="addSpell"
mini>
</paper-fab>
</core-tooltip>
{{/fabMenu}}
{{/if}}
</template>

View File

@@ -84,39 +84,35 @@ Template.spells.helpers({
},
cantCast: function(level, char){
for (var i = level; i <= 9; i++){
if (char.attributeValue("level" + i + "SpellSlots") > 0){
if (Characters.calculate.attributeValue(char._id, "level" + i + "SpellSlots") > 0){
return false;
}
}
return true;
},
baseSlots: function(char){
return char.attributeBase("level" + this.level + "SpellSlots");
},
slots: function(char){
return char.attributeValue("level" + this.level + "SpellSlots");
},
showSlots: function(char){
return this.level && char.attributeBase("level" + this.level + "SpellSlots");
return this.level && Characters.calculate.attributeBase(
char._id, "level" + this.level + "SpellSlots"
);
},
hasSlots: function(){
for (var i = 1; i <= 9; i += 1){
if (this.attributeBase("level" + i + "SpellSlots")){
if (Characters.calculate.attributeBase(this._id, "level" + i + "SpellSlots")){
return true;
}
}
return false;
},
slotBubbles: function(char){
var baseSlots = char.attributeBase("level" + this.level + "SpellSlots");
var currentSlots = char.attributeValue("level" + this.level + "SpellSlots");
var baseSlots = Characters.calculate.attributeBase(char._id, "level" + this.level + "SpellSlots");
var currentSlots = Characters.calculate.attributeValue(char._id, "level" + this.level + "SpellSlots");
var slotsUsed = baseSlots - currentSlots;
var bubbles = [];
var i;
for (i = 0; i < currentSlots; i++){
bubbles.push({
icon: "radio-button-on",
disabled: i !== currentSlots - 1, //last full slot not disabled
disabled: i !== currentSlots - 1 || !canEditCharacter(char._id), //last full slot not disabled
attribute: "level" + this.level + "SpellSlots",
charId: char._id,
});
@@ -124,7 +120,7 @@ Template.spells.helpers({
for (i = 0; i < slotsUsed; i++){
bubbles.push({
icon: "radio-button-off",
disabled: i !== 0, //first empty slot not disabled
disabled: i !== 0 || !canEditCharacter(char._id), //first empty slot not disabled
attribute: "level" + this.level + "SpellSlots",
charId: char._id,
});
@@ -143,15 +139,15 @@ Template.spells.events({
var char = Characters.findOne(this.charId);
if (event.currentTarget.icon === "radio-button-off"){
if (
char.attributeValue(this.attribute) <
char.attributeBase(this.attribute)
Characters.calculate.attributeValue(char._id, this.attribute) <
Characters.calculate.attributeBase(char._id, this.attribute)
){
modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = 1;
Characters.update(this.charId, modifier, {validate: false});
}
} else {
if (char.attributeValue(this.attribute) > 0){
if (Characters.calculate.attributeValue(char._id, this.attribute) > 0){
modifier = {$inc: {}};
modifier.$inc[this.attribute + ".adjustment"] = -1;
Characters.update(this.charId, modifier, {validate: false});
@@ -170,7 +166,7 @@ Template.spells.events({
heroId: charId + stat,
});
},
"tap .containerTop": function(event){
"tap .spellList .top": function(event){
GlobalUI.setDetail({
template: "spellListDialog",
data: {spellListId: this._id, charId: this.charId},
@@ -184,7 +180,7 @@ Template.spells.events({
heroId: this._id,
});
},
"tap #addSpellList": function(event){
"tap .addSpellList": function(event){
var charId = this.charId;
SpellLists.insert({
name: "New SpellList",
@@ -201,7 +197,7 @@ Template.spells.events({
}
});
},
"tap #addSpell": function(event){
"tap .addSpell": function(event){
var charId = this.charId;
var listId = SpellLists.findOne({charId: this._id})._id;
Spells.insert({

View File

@@ -1,34 +0,0 @@
.card.double {
display: flex;
}
.card.double > div{
vertical-align: top;
padding: 16px;
}
.abilityScore {
width: 70px;
text-align: center;
background-color: #D50000;
padding: 16px;
position: relative;
border-radius: 2px 0 0 2px;
}
#stats .card {
padding: 0;
}
.abilityCardRight {
flex-grow: 1;
padding-right: 0;
}
.abilityCardRight hr{
margin: 8px 0 8px -16px;
}
.abilityCardRight h1{
margin-bottom: 8px;
}

View File

@@ -1,122 +1,16 @@
<template name="abilityMiniCard">
<paper-shadow class="card double abilityMiniCard"
hero-id="main" {{detailHero ability ../_id}}>
<div class="abilityScore white-text {{color}}"
<div>
<paper-shadow class="card abilityMiniCard clickable"
hero-id="main" {{detailHero ability ../_id}}
layout horizontal>
<div class="left white-text {{color}}"
hero-id="toolbar" {{detailHero ability ../_id}}>
<h1 class="display1">{{../attributeValue ability}}</h1>
<h2>{{../abilityMod ability}}</h2>
<div class="display1">{{characterCalculate "attributeValue" ../_id ability}}</div>
<div class="title">{{abilityMod}}</div>
</div>
<div class="abilityCardRight subhead" layout horizontal center>
<div class="right subhead" layout horizontal center>
{{title}}
</div>
<paper-ripple fit></paper-ripple>
</paper-shadow>
</div>
</template>
<template name="strengthCard">
<paper-shadow class="card double">
<div class="abilityScore red white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "strength"}}</h1>
<h2>{{abilityMod "strength"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Strength</h1>
{{> skillRow name="Save" skill="strengthSave"}}
<hr>
{{> skillRow name="Athletics" skill="athletics"}}
</div>
</paper-shadow>
</template>
<template name="dexterityCard">
<paper-shadow class="card double">
<div class="abilityScore green white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "dexterity"}}</h1>
<h2>{{abilityMod "dexterity"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Dexterity</h1>
{{> skillRow name="Save" skill="dexteritySave"}}
<hr>
{{> skillRow name="Acrobatics" skill="acrobatics"}}
{{> skillRow name="Sleight of Hand" skill="sleightOfHand"}}
{{> skillRow name="Stealth" skill="stealth"}}
</div>
</paper-shadow>
</template>
<template name="constitutionCard">
<paper-shadow class="card double">
<div class="abilityScore deep-orange white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "constitution"}}</h1>
<h2>{{abilityMod "constitution"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Constitution</h1>
{{> skillRow name="Save" skill="constitutionSave"}}
<hr>
</div>
</paper-shadow>
</template>
<template name="intelligenceCard">
<paper-shadow class="card double">
<div class="abilityScore indigo white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "intelligence"}}</h1>
<h2>{{abilityMod "intelligence"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Intelligence</h1>
{{> skillRow name="Save" skill="intelligenceSave"}}
<hr>
{{> skillRow name="Arcana" skill="arcana"}}
{{> skillRow name="History" skill="history"}}
{{> skillRow name="Investigation" skill="investigation"}}
{{> skillRow name="Nature" skill="nature"}}
{{> skillRow name="Religion" skill="religion"}}
</div>
</paper-shadow>
</template>
<template name="wisdomCard">
<paper-shadow class="card double">
<div class="abilityScore purple white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "wisdom"}}</h1>
<h2>{{abilityMod "wisdom"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Wisdom</h1>
{{> skillRow name="Save" skill="wisdomSave"}}
<hr>
{{> skillRow name="Animal Handling" skill="animalHandling"}}
{{> skillRow name="Insight" skill="insight"}}
{{> skillRow name="Medicine" skill="medicine"}}
{{> skillRow name="Perception" skill="perception" showPassive="true"}}
{{> skillRow name="Survival" skill="survival"}}
</div>
</paper-shadow>
</template>
<template name="charismaCard">
<paper-shadow class="card double">
<div class="abilityScore pink white-text">
{{> ripple color="#eee"}}
<h1 class="display1">{{attributeValue "charisma"}}</h1>
<h2>{{abilityMod "charisma"}}</h2>
</div>
<div class="abilityCardRight">
<h1>Charisma</h1>
{{> skillRow name="Save" skill="charismaSave"}}
<hr>
{{> skillRow name="Deception" skill="deception"}}
{{> skillRow name="Intimidation" skill="intimidation"}}
{{> skillRow name="Performance" skill="performance"}}
{{> skillRow name="Persuasion" skill="persuasion"}}
</div>
</paper-shadow>
</template>

View File

@@ -0,0 +1,9 @@
Template.abilityMiniCard.helpers({
abilityMod: function() {
return signedString(
Characters.calculate.abilityMod(
Template.parentData()._id, this.ability
)
);
}
});

View File

@@ -1,9 +1,11 @@
Template.addTHPDialog.events({
"tap #addButton": function(event, instance){
var max = +instance.find("#quantityInput").value;
if (!max || max < 0) max = 0;
TemporaryHitPoints.insert({
charId: this.charId,
name: instance.find("#nameInput").value,
maximum: +instance.find("#quantityInput").value,
maximum: max,
deleteOnZero: !!instance.find("#deleteWhenZeroCheckbox").checked,
});
}

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