Compare commits

..

437 Commits

Author SHA1 Message Date
Thaum Rystra
e0fc5abe7b Improved invite landing page UI 2020-05-19 00:28:31 +02:00
Thaum Rystra
18c9474570 Separated attacks and actions into two separate cards 2020-05-19 00:19:23 +02:00
Thaum Rystra
4a039e769b Added viewers, tree node viewers and fixed forms for new damage schema 2020-05-19 00:15:05 +02:00
Thaum Rystra
93ab67a91b Fixed failure to recompute creature on tree reorganize 2020-05-18 23:53:34 +02:00
Thaum Rystra
4352ca5f0d Inventory items can now be equipped 2020-05-18 23:03:34 +02:00
Thaum Rystra
fe11c9ec23 Tree nodes are no longer lazy - smoother animations when expanding nodes 2020-05-18 20:22:48 +02:00
Thaum Rystra
37d6b32ea3 Action 'uses' now shows up as the computed value 2020-05-18 20:22:00 +02:00
Thaum Rystra
7592332637 Fixed setDocToLastOrder not working with new ordering design 2020-05-18 20:09:21 +02:00
Thaum Rystra
397ff82c43 Organizing the tree now causes a character recomputation where relevant 2020-05-18 19:58:28 +02:00
Thaum Rystra
7e3815a699 Fixed glitchy reordering of trees 2020-05-18 02:03:31 +02:00
Thaum Rystra
9214529284 rewrote entire ordering structure for ancestor trees 2020-05-18 02:03:14 +02:00
Thaum Rystra
60f5588e7d Prevented description viewers from keeping zombie text after description is deleted 2020-05-17 18:13:06 +02:00
Thaum Rystra
ad3bec3521 Began working on bringing forms and UI in line with data structure overhaul 2020-05-17 00:06:19 +02:00
Thaum Rystra
ca5ded7ded Class levels now recompute properly 2020-05-16 22:51:17 +02:00
Thaum Rystra
5c0a2a4d6c Overhauled computations to allow for toggles :'( that sucked 2020-05-16 22:03:21 +02:00
Thaum Rystra
7024adecaf created a general way to fetch the active properties of an ancestor 2020-05-16 17:08:57 +02:00
Thaum Rystra
9b6f259358 Library node edit form no longer uses stored variants 2020-05-16 14:27:54 +02:00
Thaum Rystra
3642d1d249 Workaround for Firefox not obeying break-inside: avoid; 2020-05-16 14:19:13 +02:00
Thaum Rystra
cd2727b61c Can now link google account 2020-05-16 14:07:22 +02:00
Thaum Rystra
acb9dc342a Improved handling of character avatars, added portraits 2020-05-16 13:40:54 +02:00
Thaum Rystra
d59d8cb54f Library insert forms no longer used stored variants 2020-05-16 12:51:59 +02:00
Thaum Rystra
2c988b8717 Fixed an error where incorrectly targeted effects would cause computation error 2020-05-15 17:27:34 +02:00
Thaum Rystra
3af48649f7 Added some guards against missing properties 2020-05-15 16:51:58 +02:00
Thaum Rystra
79e03e0e63 Separated tool, weapon, armor, and language proficiencies into separate cards 2020-05-15 16:43:37 +02:00
Thaum Rystra
2d788f0c07 ability scores now pass on their skill effects to checks and skills 2020-05-15 16:38:28 +02:00
Thaum Rystra
891fd00b5f Skills now correctly denormalise their passive bonus, conditional benefits, advantage, and fail effects 2020-05-15 16:23:57 +02:00
Thaum Rystra
41b05064c8 login redirects now carry over to the register page 2020-05-15 15:16:59 +02:00
Thaum Rystra
cf110db67d Effect stats input now uses chips 2020-05-15 15:09:46 +02:00
Thaum Rystra
db696574f5 Comboboxes now clear search text when selecting an option 2020-05-15 15:09:34 +02:00
Thaum Rystra
4478628200 Reworked how bare symbols are handled, which should fix simplification 2020-05-15 14:44:08 +02:00
Thaum Rystra
cd8a557120 Let effects autofill skills as well as attributes 2020-05-15 14:29:07 +02:00
Thaum Rystra
5f95471bb6 Added transitions to tree tab property viewer 2020-05-15 14:23:32 +02:00
Thaum Rystra
a62726ae3a Allowed long property viewers in tree tab to scroll 2020-05-15 13:57:09 +02:00
Thaum Rystra
59fa5bcd8c Improved spacing on cards 2020-05-15 13:54:43 +02:00
Thaum Rystra
4b3f068d87 Fixed character sheet tabs not taking up the full screen height 2020-05-15 13:38:31 +02:00
Thaum Rystra
a771d896a8 Prevented dialog titles from overflowing 2020-05-15 13:31:07 +02:00
Thaum Rystra
b439befd47 Stopped tree view overflowing horizontally 2020-05-15 13:23:34 +02:00
Thaum Rystra
b44a18c28c Made tree tab work on mobile. prevented overflow of long titles 2020-05-15 13:18:27 +02:00
Thaum Rystra
bae2f4181a removed old default docs 2020-05-15 11:10:30 +02:00
Thaum Rystra
9b46a91641 Fixed missing Creature imports 2020-05-15 11:10:21 +02:00
Thaum Rystra
fc9467177b hotfix to prevent character sheet going black if DamageMultipliers isn't present 2020-05-14 15:32:54 +02:00
Thaum Rystra
136dd5fd29 Removed stray logging 2020-05-14 15:25:58 +02:00
Thaum Rystra
cb34363a4e Damage multipliers now compute and show up on the character sheet 2020-05-14 15:22:23 +02:00
Thaum Rystra
a4d6adacff Fixed damage multiplier forms and viewers 2020-05-14 13:43:12 +02:00
Thaum Rystra
29588a87d0 Fixed dependency loops causing a stack overflow. Added level variable 2020-05-14 13:32:46 +02:00
Thaum Rystra
97fcb76454 Added if function 2020-05-13 10:42:20 +02:00
Thaum Rystra
9069ee8e35 Improved effect, skill, and attribute viewers 2020-05-13 10:28:39 +02:00
Thaum Rystra
170bac6934 Pruned unused components 2020-05-13 09:43:01 +02:00
Thaum Rystra
809426b183 Improved effect components 2020-05-13 09:42:24 +02:00
Thaum Rystra
e8728166a9 Moved effect components to components folder 2020-05-13 09:34:00 +02:00
Thaum Rystra
5046a847cf Quality pass over all publications, fixed public documents permission error 2020-05-13 09:29:29 +02:00
Thaum Rystra
b6c7ea8c4f Improved handling of tiers, made guest tier funcitonal, improved invites 2020-05-12 15:28:43 +02:00
Thaum Rystra
bbda0ea1b6 Invites can now be managed to some extent 2020-05-12 14:11:43 +02:00
Thaum Rystra
47206ccfc4 Continued implementing sharing 2020-05-12 12:27:24 +02:00
Thaum Rystra
dd213feb0a Added label to username edit field 2020-05-09 13:53:47 +02:00
Thaum Rystra
30ab216dc1 removed redundant publications 2020-05-09 13:45:11 +02:00
Thaum Rystra
ea88a9c41e Libraries can now be shared from the library edit dialog 2020-05-09 13:40:01 +02:00
Thaum Rystra
f0e22dc1ca Made feedback page go to discord 2020-05-09 13:33:33 +02:00
Thaum Rystra
8c608937bb Username can now be changed 2020-05-09 13:15:03 +02:00
Thaum Rystra
c3ed3d55ce Locked friends page since it's not implemented 2020-05-08 17:12:03 +02:00
Thaum Rystra
323cac9405 corrected not implemented page to 'beta' instead of 'alpha' 2020-05-08 16:58:01 +02:00
Thaum Rystra
62689174e2 removed stray console.log 2020-05-08 16:31:58 +02:00
Thaum Rystra
6c8e9037e1 Fixed function being replaced with object['value'] accessors 2020-05-08 16:20:00 +02:00
Thaum Rystra
e934f92e8b Removed starter characters from home page, fixed social buttons not displaying 2020-05-08 16:10:04 +02:00
Thaum Rystra
8aea5c4fc6 Fixed property viewer showing zombie fields from previously viewed properties 2020-05-08 16:04:49 +02:00
Thaum Rystra
5f87bbc4f5 Used custom labels on tree tab buttons to prevent tooltip formatting shenanigans 2020-05-08 15:59:59 +02:00
Thaum Rystra
4d0ff52853 Removing effects or other properties now correctly triggers a recalculation of the character 2020-05-08 15:51:54 +02:00
Thaum Rystra
6506cfd727 Removed extraneous variable name field from spell list form 2020-05-08 15:49:16 +02:00
Thaum Rystra
f486c3f176 Fixed armor proficiency skilltype not existing 2020-05-08 15:47:36 +02:00
Thaum Rystra
eed11e8833 fixed attacks not displaying if no actions display 2020-05-08 15:43:50 +02:00
Thaum Rystra
46585406df Fixed buffs not being insertable 2020-05-08 15:42:31 +02:00
Thaum Rystra
5597acb0ea Fixed spells not persisting checkbox values 2020-05-08 15:37:07 +02:00
Thaum Rystra
3fa9077124 fixed typo, added tier 2020-05-07 16:14:50 +02:00
Thaum Rystra
cb8d311fdc Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2020-05-07 15:53:52 +02:00
Thaum Rystra
eb3f8c9105 fixed countdown page for not-logged in users 2020-05-07 15:53:24 +02:00
Thaum Rystra
82a2241e4d fixed countdown page for not-logged in users 2020-05-07 15:50:44 +02:00
Thaum Rystra
3804323322 Added countdown page 2020-05-07 15:42:42 +02:00
Thaum Rystra
31185a4b12 Invite system created, mostly done, but timeboxed for now 2020-05-07 14:52:18 +02:00
Thaum Rystra
4ca47d3a62 Improved account page 2020-05-02 18:22:04 +02:00
Thaum Rystra
26662b939c Improved data risk warning 2020-05-02 18:21:55 +02:00
Thaum Rystra
48e54c42b4 Improved usability and UI for Features. Fixed embedded computations 2020-05-02 17:54:22 +02:00
Thaum Rystra
d649fb9d54 Greatly improved look and feel of site navigation 2020-05-02 17:09:56 +02:00
Thaum Rystra
073578b90d Made all login Patreon only, limited some functionality to $5 patrons 2020-04-30 22:38:27 +02:00
Thaum Rystra
1ca6bc834a Removed variable names from items, now only use tags. Items can also be attuned 2020-04-30 14:52:41 +02:00
Thaum Rystra
4180e153dd Computation now handles class levels 2020-04-30 14:26:41 +02:00
Thaum Rystra
15db76a2fe Added proficiencies for languages tools and weapons to the stats tab 2020-04-30 14:26:28 +02:00
Thaum Rystra
1763358642 Ui tweaks 2020-04-29 14:00:58 +02:00
Thaum Rystra
f63faa867b Fixed deleted health bars still showing on character sheet 2020-04-29 12:48:21 +02:00
Thaum Rystra
a2fe8d950a Fixed computations throwing errors when not provided with context 2020-04-29 12:44:09 +02:00
Thaum Rystra
d63565633e Zero value stats now appear in character sheet 2020-04-29 11:10:23 +02:00
Thaum Rystra
4fd62d17bd Updated markdown santizing because the old implementation was deprecated 2020-04-29 11:03:47 +02:00
Thaum Rystra
ed9cfee9f9 Added autocomplete for fields that expect variable names 2020-04-29 10:53:06 +02:00
Thaum Rystra
966fcc449d Hit dice now explicitly set their size 2020-04-28 17:12:22 +02:00
Thaum Rystra
eae35062d0 Allowed attributes to take calculations as their base value 2020-04-28 16:23:52 +02:00
Thaum Rystra
ee0fb72d56 Ensured that properties that are removed don't show up on the character sheet 2020-04-28 14:51:53 +02:00
Thaum Rystra
528e53fc9b Fixed proficiency calculations 2020-04-27 13:56:04 +02:00
Thaum Rystra
17c9d270e6 Fixed modifiers not being computed and displayed 2020-04-27 13:19:23 +02:00
Thaum Rystra
a2bae8d12a Fixed an issue with clearing forms not correctly unsetting a value 2020-04-26 11:37:51 +02:00
Thaum Rystra
13cb9253c3 Added resource forms to actions 2020-04-26 10:28:40 +02:00
Thaum Rystra
88ed912d18 in mathjs node.eval is deprecated, replaced with .evaluate 2020-04-26 10:28:23 +02:00
Thaum Rystra
6746f5f989 Added results viewer 2020-04-26 09:42:32 +02:00
Thaum Rystra
b1328e4cf5 Action and attack components show up correctly on character sheet 2020-04-24 15:10:58 +02:00
Thaum Rystra
7bf0e959d7 reinstalled all npm packages 2020-04-24 14:07:42 +02:00
Thaum Rystra
ed35d2e984 Began work on viewers for attacks and actions 2020-04-23 19:32:48 +02:00
Thaum Rystra
a6bdfe247c Removed multipliers from reset field 2020-04-23 18:55:13 +02:00
Thaum Rystra
a86176a20d fixed action reset menu clearing throwing an error 2020-04-23 15:37:30 +02:00
Thaum Rystra
4e57bd4a73 Unified the add buttons for results as a single menu button 2020-04-23 15:37:05 +02:00
Thaum Rystra
95bfcd79c9 Added UI backend that can do computations with context 2020-04-23 14:26:05 +02:00
Thaum Rystra
7416101a34 Computation writes variables available/computed to the creature document 2020-04-16 17:57:18 +02:00
Thaum Rystra
2164174218 Fixed all effects getting added to unassigned effects even when they were assigned 2020-04-16 15:48:07 +02:00
Thaum Rystra
1717ee4bc7 Made spells into a special kind of action 2020-04-16 15:21:31 +02:00
Thaum Rystra
e06196a54c Fixed buffs not being able to be added to actions after creation 2020-04-04 19:09:00 +02:00
Thaum Rystra
6008d8b47a Made parent target of forms optional again 2020-04-04 18:44:57 +02:00
Thaum Rystra
e77513110b Refactored all forms for updated code style 2020-04-04 18:40:08 +02:00
Thaum Rystra
1856e90d12 Changed data structure around attacks and their consumed resources 2020-04-04 18:18:38 +02:00
Thaum Rystra
97111741bf updated dependencies 2020-04-04 14:30:22 +02:00
Thaum Rystra
fc5576397a Added quote rules to eslint 2020-04-04 11:45:08 +02:00
Thaum Rystra
53b1f16d2f replaced jscsrc and jshint with eslint 2020-04-04 11:31:07 +02:00
Stefan Zermatten
2981813751 Creature computations working again 2020-03-23 11:59:04 +02:00
Stefan Zermatten
74fef2bd39 Refactored computations again, split into multiple files, lots still to do 2020-03-17 16:13:18 +02:00
Stefan Zermatten
1a0c2bca78 Began refactoring character computations 2020-03-16 17:28:53 +02:00
Stefan Zermatten
5ed8e08993 Fixed effects not being able to be added to stored buffs 2020-03-16 14:16:50 +02:00
Stefan Zermatten
e3c6949491 Added search to character tree tab 2020-03-13 16:09:43 +02:00
Stefan Zermatten
e0e7693fff Improved app layout a bit 2020-03-13 14:56:59 +02:00
Stefan Zermatten
7fe2292c2a Added persona page 2020-03-13 14:56:43 +02:00
Stefan Zermatten
9290c9570c Moved organize buttons for inventory and spells tab to the right 2020-03-13 12:23:23 +02:00
Stefan Zermatten
24725381d7 Added spells tab 2020-03-13 12:02:57 +02:00
Stefan Zermatten
c1aacb9ebe Containers that have ancestor containers no longer show up in top level inventory 2020-03-13 11:42:19 +02:00
Stefan Zermatten
0e71d1c719 Moved spell lists to advanced section of spells form 2020-03-13 11:39:30 +02:00
Stefan Zermatten
2381769ea2 Inventory now uses filtered tree views to display items in containers 2020-03-13 11:23:19 +02:00
Stefan Zermatten
adfe1dc613 Markdown now works in property descriptions for viewers 2020-03-13 10:12:01 +02:00
Stefan Zermatten
27300190d3 Clicking container headers now works as expected 2020-03-12 16:51:50 +02:00
Stefan Zermatten
d90894d7c6 Fixed items not showing up in containers 2020-03-12 16:43:06 +02:00
Stefan Zermatten
9e7c1ce405 Get vue-meteor-tracker to freeze meteor data to stop a infinite flush loops 2020-03-12 16:40:37 +02:00
Stefan Zermatten
d00ff000ce Added inventory tab 2020-03-12 15:51:49 +02:00
Stefan Zermatten
e26031368d fixed inserting library node not finding the node to animate dialog to 2020-03-09 15:07:08 +02:00
Stefan Zermatten
9ac6b510e4 Added tree of sub-properties to property dialog 2020-03-09 14:29:11 +02:00
Stefan Zermatten
e67b4c72e3 Removed stray console log 2020-03-09 12:58:14 +02:00
Stefan Zermatten
1c700121ca removed accounts-meld 2020-03-09 12:56:40 +02:00
Stefan Zermatten
8e4694f63e Removed enabled/disabled property of features 2020-03-09 12:44:30 +02:00
Stefan Zermatten
827d567ac2 Fixed stats being displayed for all subscribed creatures instead of the current creature 2020-03-09 12:34:11 +02:00
Stefan Zermatten
43a08eb034 Health bar refactored to not use keycodes, which are deprecated 2020-03-09 12:08:39 +02:00
Stefan Zermatten
ea5ac42ec0 fixed padding on dark mode button 2020-03-09 10:47:39 +02:00
Stefan Zermatten
1be09e48ef Clearable selects now unset the property when cleared 2020-03-09 10:44:40 +02:00
Stefan Zermatten
625455da09 Re-organized ui/properties folder 2020-03-06 10:15:38 +02:00
Stefan Zermatten
4a25c22b64 Fixed spell and resource card adjustments 2020-03-06 10:02:39 +02:00
Stefan Zermatten
53e9be1407 Removed fibres from dependencies, meteor should come with it 2020-03-06 09:15:04 +02:00
Stefan Zermatten
7055f4d990 Added node versions to the package file to help the host build the app 2020-03-06 08:57:13 +02:00
Stefan Zermatten
797b6a0d88 Updated Meteor and npm packages 2020-03-05 15:31:13 +02:00
Stefan Zermatten
7afbfa1816 Fixed no-op bulk writes 2020-03-05 14:28:32 +02:00
Stefan Zermatten
d6877905c9 removed empty console error 2020-03-05 14:09:21 +02:00
Stefan Zermatten
69e8a307fc Removed a bunch of console noise 2020-03-05 14:05:32 +02:00
Stefan Zermatten
66e70c8c94 Libraries can now be deleted 2020-03-05 14:00:16 +02:00
Thaum Rystra
dfa3b057b0 Fixed property adjustments on the stats page 2020-03-04 09:56:06 +02:00
Thaum Rystra
f3d86ef274 Characters can now be deleted 2020-03-03 17:49:35 +02:00
Thaum Rystra
46a0e92402 Improved sharing dialog, setting a sheet as public now working 2020-03-03 17:00:05 +02:00
Stefan Zermatten
d0c8131d5f Fixed syntax errors breaking the build 2020-03-02 16:35:16 +02:00
Stefan Zermatten
5578dca6e9 began implementing sharing dialog 2020-03-02 16:31:57 +02:00
Stefan Zermatten
724f9574a2 Added basic creature document editing UI 2020-03-02 15:45:55 +02:00
Stefan Zermatten
2bd7c2908f Added remove creature method 2020-03-02 10:03:58 +02:00
Thaum Rystra
692a7983f8 Fixed inserting new characters. No wizard though 2020-02-29 17:31:21 +02:00
Stefan Zermatten
acb9369100 Removed floating action button from features tab 2020-02-18 15:44:32 +02:00
Stefan Zermatten
21dbab1877 Improved effect form and view 2020-02-18 14:05:15 +02:00
Stefan Zermatten
86926e593c Got creature recomputation to do things in sheet 2020-02-18 13:27:31 +02:00
Stefan Zermatten
81cec77c9e Added write methods to creature properties 2020-02-18 12:14:38 +02:00
Stefan Zermatten
acb8a073de Updated packes 2020-02-18 12:14:24 +02:00
Stefan Zermatten
6996d2a99e updated node packages 2020-02-10 09:53:59 +02:00
Stefan Zermatten
83b810ce3d Specified that variable names must be used in ammunition references 2020-01-27 13:50:27 +02:00
Stefan Zermatten
328480d2c5 Sidebar no longer refers to "alpha" build in preparation for beta release 2020-01-27 13:50:11 +02:00
Stefan Zermatten
8e4c6252cd Added a universal dialog for creature properties 2020-01-27 13:49:50 +02:00
Stefan Zermatten
c3cc4c881d Migrated creature computations to use the new data structure for creature properties 2020-01-27 11:21:52 +02:00
Stefan Zermatten
4ee7307e34 Migrating character sheet to new data format 2020-01-16 08:55:53 +02:00
Stefan Zermatten
eabc0aa32e Groundwork for default libraries and slots 2019-11-13 11:54:27 +02:00
Stefan Zermatten
ae0b060f01 Added inserting library subtrees as character property subtrees 2019-11-05 10:56:04 +02:00
Stefan Zermatten
79a4488a28 Fixed a layout issue with library and node view 2019-11-04 13:38:42 +02:00
Stefan Zermatten
dfa13ef2c3 Disabled character list 2019-11-04 13:30:46 +02:00
Stefan Zermatten
f6d80f6ae4 Made big improvements to library page in preparation of adding library nodes to character sheets 2019-11-04 13:27:31 +02:00
Stefan Zermatten
f4d613a20b Started working on getting creature property insertion working 2019-09-27 11:06:33 +02:00
Stefan Zermatten
73f193460d Fixed Roll form 2019-09-26 11:29:46 +02:00
Stefan Zermatten
ba4cc1a496 Fixed more odering bugs, still flashes before being sorted though :( 2019-09-25 16:08:01 +02:00
Stefan Zermatten
21030d186a Added saving throws, messed with data structure a bunch, and updated forms to suit 2019-09-25 14:38:20 +02:00
Stefan Zermatten
4fcae9b3f9 Fixed a bug where moving documents around deleted docs breaks the ordering 2019-09-25 14:37:54 +02:00
Stefan Zermatten
66d11d58f3 Data changes to make attacks top level objects 2019-09-19 16:03:46 +02:00
Stefan Zermatten
63a20b2bef Moved subschema folder 2019-09-16 10:24:13 +02:00
Stefan Zermatten
88a2a506b0 Renamed RollResult to RollResults 2019-09-16 09:39:04 +02:00
Stefan Zermatten
710c578119 Fiddled with rolls and saves 2019-09-02 13:49:01 +02:00
Stefan Zermatten
a8b3fc3f2f Started restructuring the library with attacks, saves, and limited parenting 2019-08-12 16:42:30 +02:00
Stefan Zermatten
6f4710bee3 Roll modifiers are effects that target rolls based on their tags 2019-08-06 16:37:52 +02:00
Stefan Zermatten
50621ca269 Damage multipliers can now target or exclude tags 2019-08-06 16:37:38 +02:00
Stefan Zermatten
16d61eb708 Effects can now impact multiple stats 2019-08-06 16:36:37 +02:00
Stefan Zermatten
b110eadb7c Rerouted pages that shouldn't work to an info alert making that clear 2019-08-06 13:17:04 +02:00
Stefan Zermatten
45c84e28a3 Improved property viewers, added some new ones 2019-08-06 13:16:46 +02:00
Stefan Zermatten
b70634f5de removed old property components 2019-08-06 10:37:37 +02:00
Stefan Zermatten
e3f18fab69 Moved sidebar component to layout folder 2019-08-06 10:37:25 +02:00
Stefan Zermatten
56f3af3c4b Client can't read node environment... changed storybook flag to public setting 2019-08-06 10:13:55 +02:00
Stefan Zermatten
a58def26d1 Added storybook to production if SHOW_STORYBOOK env variable is true 2019-08-06 10:03:27 +02:00
Stefan Zermatten
0014c691d2 Added alternative text for missing property viewers 2019-08-05 12:10:50 +02:00
Stefan Zermatten
5436b12108 Added more property viewers 2019-08-05 12:04:26 +02:00
Stefan Zermatten
d45b184170 moved effect viewer 2019-08-02 13:00:16 +02:00
Stefan Zermatten
0b184c4e85 Library node editing now includes editing sub-documents and soft removal 2019-08-02 12:35:59 +02:00
Stefan Zermatten
745f4fd353 Library nodes can now be edited :D 2019-08-02 10:47:29 +02:00
Stefan Zermatten
3c4f3e26f8 Added edit button to library node view 2019-08-01 16:06:43 +02:00
Stefan Zermatten
67ea67148f Added a description field to attributes 2019-08-01 15:45:15 +02:00
Stefan Zermatten
f37ff919fb Fixed library node creation dialog 2019-08-01 15:25:52 +02:00
Stefan Zermatten
76b6501b31 Improved library view layout 2019-08-01 14:39:15 +02:00
Stefan Zermatten
229a5dddcf Added attribute property viewer, incomplete 2019-08-01 14:03:51 +02:00
Stefan Zermatten
e29c77dc67 Changed experience title to name for consistency 2019-08-01 14:03:29 +02:00
Stefan Zermatten
c87a3a3f60 Moved ui/forms to ui/properties/forms 2019-08-01 12:07:57 +02:00
Stefan Zermatten
9f7d6b8ae7 began work on generalized property viewer 2019-08-01 12:03:40 +02:00
Stefan Zermatten
4ccf999fc7 Trees can now do selection 2019-08-01 12:03:15 +02:00
Stefan Zermatten
549418b395 Iteration on library UI 2019-07-31 15:04:52 +02:00
Stefan Zermatten
14fe48efb3 Moved imports/ui/creature/properties to /imports/ui/properties 2019-07-31 12:02:26 +02:00
Stefan Zermatten
18eeaf4884 Added back button to library page 2019-07-31 12:00:49 +02:00
Stefan Zermatten
4f93ad3e9b Trees can now be freely re-arranged :D 2019-07-31 11:52:11 +02:00
Stefan Zermatten
d0304da4fd Began making generic tree re-arranging methods, still buggy 2019-07-30 16:47:21 +02:00
Stefan Zermatten
4b7ff2146f Purged all references to the schema factory, use SCHEMA_OPTIONS constant instead 2019-07-30 15:21:30 +02:00
Stefan Zermatten
2385b69720 Removed all separate property collections to be replaced with a single "creature property" collection 2019-07-30 15:13:39 +02:00
Stefan Zermatten
31bc3663a7 Moved properties out of creature folder, since they apply to library nodes as well now 2019-07-30 14:50:08 +02:00
Stefan Zermatten
cbdd72e09b Some changes to how parenting and ordering of docs interface 2019-07-30 14:48:49 +02:00
Stefan Zermatten
438f128641 Moved parenting folder from /api/creatures/ to /api/ 2019-07-30 13:07:31 +02:00
Stefan Zermatten
3460cbf5a0 Significantly improved tree view of libraries 2019-07-30 12:49:20 +02:00
Stefan Zermatten
da561bfc83 Added night mode to account settings 2019-07-30 10:09:01 +02:00
Stefan Zermatten
eb63a7a7f3 made XP optional in experiences, so that experiences can just be journal entries 2019-07-30 10:01:23 +02:00
Stefan Zermatten
b5ceb7fad0 updated npm packages 2019-07-30 09:59:18 +02:00
Stefan Zermatten
fb5b2b8ada Removed creature from property selector, creatures aren't properties 2019-07-30 09:59:10 +02:00
Stefan Zermatten
ea628ed379 Fixed some broken switches 2019-07-29 13:25:58 +02:00
Stefan Zermatten
d35fa447a3 Added item and container forms 2019-07-29 13:25:47 +02:00
Stefan Zermatten
73b43574ee Added spell and spell list forms 2019-07-29 12:49:19 +02:00
Stefan Zermatten
02cb690325 Spells now reference their spell lists directly 2019-07-25 15:44:45 +02:00
Stefan Zermatten
5662ce3666 Added tags to actions 2019-07-25 14:04:36 +02:00
Stefan Zermatten
45e36ac3d4 Added skill form, abstracted proficiency selection 2019-07-23 12:37:30 +02:00
Stefan Zermatten
812a6679d5 Added roll form 2019-07-23 12:02:19 +02:00
Stefan Zermatten
1bfb48c672 Added forms for Class level, damage multiplier, experience, folder, note, proficiency 2019-07-23 11:28:26 +02:00
Stefan Zermatten
946b47ea61 Gave buff targets a default value 2019-07-22 13:51:32 +02:00
Stefan Zermatten
bd3f676919 Fixed some import errors 2019-07-22 13:45:30 +02:00
Stefan Zermatten
4062d79e90 Reorganized forms into their own folder 2019-07-22 13:05:11 +02:00
Stefan Zermatten
dfa302a4a9 Completed Action form, effects form, reworked form event api 2019-07-22 12:43:57 +02:00
Stefan Zermatten
baa1c0967c Update meteor and npm packages to fix security vulnerabilities 2019-07-19 14:27:34 +02:00
Stefan Zermatten
a0dc36557c Added action form, but without buffs 2019-07-19 14:07:22 +02:00
Stefan Zermatten
0c002ae5cd Moved some fields to advanced section of attribute form 2019-07-18 16:25:52 +02:00
Stefan Zermatten
11d3b0fa8d Improved style of attribute form 2019-07-18 14:36:39 +02:00
Stefan Zermatten
d28d6de684 Inserting library nodes into libraries now works as expected 2019-07-03 14:19:16 +02:00
Stefan Zermatten
93d8a8d33e Library attribute insert form complete 2019-07-02 17:28:07 +02:00
Stefan Zermatten
4abb5edbf3 Got library attribute form onto the screen :D 2019-06-28 14:48:02 +02:00
Stefan Zermatten
9757da2cae Started work on library node insert forms 2019-06-27 16:52:28 +02:00
Stefan Zermatten
bd4fb58935 updated packages 2019-06-24 10:28:44 +02:00
Stefan Zermatten
1add44f0e7 renamed SmartInput to SmartInputMixin 2019-06-24 10:28:33 +02:00
Stefan Zermatten
99db45e522 Added function to convert libraryNode lists into forest of object trees 2019-06-20 16:43:18 +02:00
Stefan Zermatten
7a59b4542e added library publication 2019-06-20 16:42:05 +02:00
Stefan Zermatten
335608f6a9 Fixed library subscription 2019-06-14 13:07:26 +02:00
Stefan Zermatten
4d47584f4f Routed library page and cleaned up errors 2019-06-13 17:07:31 +02:00
Stefan Zermatten
48fe0d3608 Started on library page UI 2019-05-31 15:36:19 +02:00
Stefan Zermatten
9a194a20cb Added library list UI 2019-05-10 13:05:21 +02:00
Stefan Zermatten
de183297fc Updated some NPM packages to fix vulnerabilities 2019-05-10 11:32:43 +02:00
Stefan Zermatten
d921ad46d8 Merge pull request #215 from pspeter3/docker-development
Add Docker Compose
2019-04-29 09:37:34 +02:00
Phips Peter
00d02a3bb5 Add Docker Compose
In order for a developer to run DiceCloud locally, all they need to do is run `docker-compose up --build`. This does take a very long time to execute but prevents a developer from needing to deal with configuration.
2019-04-25 16:48:27 -07:00
Stefan Zermatten
b0caffae1a Added better data to the Tree node list story 2019-04-24 14:05:16 +02:00
Stefan Zermatten
e71bfb2691 Improved tree view 2019-04-24 13:22:31 +02:00
Stefan Zermatten
fc24cf4a5b Improved tree view with drag and drop 2019-04-15 15:40:42 +02:00
Stefan Zermatten
062e554629 Added comments to libraries 2019-04-15 12:39:42 +02:00
Stefan Zermatten
a1d77cdaab added update method to library nodes 2019-04-15 12:27:14 +02:00
Stefan Zermatten
f4011abf7b Moved all creature related API to the creature folder 2019-04-15 12:09:37 +02:00
Stefan Zermatten
aa9802b34e Iterated on library nodes 2019-04-15 12:04:17 +02:00
Stefan Zermatten
dabb54b0a3 Refactored UI folder structure 2019-04-15 11:44:27 +02:00
Stefan Zermatten
05867c61dd Started on tree view for real 2019-04-12 13:37:47 +02:00
Thaum Rystra
ea968ad955 Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2019-04-06 10:56:57 +02:00
Thaum Rystra
b7b0ac9c00 Separated parser class nodes and began writing compile methods 2019-04-06 10:56:53 +02:00
Stefan Zermatten
ab2ea36541 Added markdown support to features 2019-04-05 13:05:45 +02:00
Stefan Zermatten
b35e08b9b4 Iterated on features UI 2019-04-03 16:50:22 +02:00
Stefan Zermatten
021a53ef36 Moved properties schema, added 'removeProperty' method 2019-04-03 16:50:12 +02:00
Stefan Zermatten
681e669e76 Refactored schemas to make properties all implicitely children 2019-04-03 12:38:01 +02:00
Stefan Zermatten
28ffcc87b4 updated package name 2019-04-03 10:28:04 +02:00
Stefan Zermatten
888a7ea592 Removed Bowerrc, nothing uses bower anymore 2019-04-03 10:27:11 +02:00
Stefan Zermatten
053a7a36a6 Began generalizing insert forms for character properties to reduce duplicated code 2019-04-01 16:58:15 +02:00
Stefan Zermatten
6d68796a11 Fixed nasty bug where mixins were bashing the schemas passed to them 2019-04-01 16:57:29 +02:00
Stefan Zermatten
18493afbbf Collated update methods into an index, fixed typo 2019-04-01 15:51:11 +02:00
Stefan Zermatten
a94f437ba8 methods now use correct mixins 2019-04-01 13:48:39 +02:00
Stefan Zermatten
d21827106c Moved parse tree classes out of grammar.js started working on compilation. Broke the build 2019-03-29 14:08:09 +02:00
Stefan Zermatten
caf7f3efb9 Moved parser into main source folders 2019-03-27 12:23:27 +02:00
Stefan Zermatten
484b73d836 Moved parser javascript into single block 2019-03-27 11:25:34 +02:00
Stefan Zermatten
ee788c952a Improved grammar 2019-03-27 11:06:43 +02:00
Stefan Zermatten
ffa98d76fc Began writing a custom parser for calculations 2019-03-26 16:32:24 +02:00
Stefan Zermatten
5bb5f047f4 Moved a lot of functionality to mixins, improved parenting 2019-03-19 15:57:21 +02:00
Stefan Zermatten
1146d56324 Fixed jshint warnings 2019-03-19 09:31:14 +02:00
Stefan Zermatten
ef83d54fd9 Reorganized folder structure, removed legacy v1 code 2019-03-14 10:10:58 +02:00
Stefan Zermatten
18e3f653f3 Added insert and update methods for all properties 2019-03-13 14:03:03 +02:00
Stefan Zermatten
572aca5906 Moved name to properties schema 2019-03-13 09:40:20 +02:00
Stefan Zermatten
f3d19d3b38 Changed instances to encounters 2019-03-13 09:31:46 +02:00
Stefan Zermatten
94f6631a7d Overhauled data models to make actions and libraries more universal 2019-03-12 16:47:20 +02:00
Stefan Zermatten
febb65a513 Re-wrote parenting, should be significantly faster, more maintainable 2019-03-08 13:57:24 +02:00
Stefan Zermatten
15c11e16ab Began adding generic child lists of effects, proficiencies, etc. 2019-03-06 11:58:40 +02:00
Stefan Zermatten
8772e539da Made initiative a skill of type "check" instead of a modifier 2019-02-27 16:08:20 +02:00
Stefan Zermatten
4e7c0c5a90 Removed skill type limit from proficiencies that are included in computation 2019-02-27 15:51:44 +02:00
Stefan Zermatten
0ae2cc5545 Changed creature computations to leverage the MathJS parser (big deal) 2019-02-27 15:49:28 +02:00
Stefan Zermatten
687b517164 Added spell slot display, improved resource card 2019-02-26 16:37:19 +02:00
Stefan Zermatten
8305596373 Added resource cards 2019-02-26 14:11:59 +02:00
Stefan Zermatten
8f624b24a9 Organised components into folders 2019-02-26 11:27:48 +02:00
Stefan Zermatten
a2432c7161 Moved attribute related components into their own folder 2019-02-26 11:20:01 +02:00
Stefan Zermatten
b7a4a3d3fa Added simple feature UI components and insertion dialog 2019-02-25 15:38:57 +02:00
Stefan Zermatten
e5ff116208 Made the stats FAB fixed instead of absolute 2019-02-21 10:57:28 +02:00
Stefan Zermatten
24f3431900 Removed all spaces around v-icon's text, because it causes alignment issues 2019-02-20 15:48:35 +02:00
Stefan Zermatten
4f402830d8 Added attribute insertion UI and API 2019-02-20 14:30:04 +02:00
Stefan Zermatten
9e208ad3b3 Dialog stack callbacks can now return a return element ID to animate to 2019-02-20 14:29:33 +02:00
Stefan Zermatten
9d027aeabf Allowed smart inputs to accept errors as props 2019-02-20 14:27:20 +02:00
Stefan Zermatten
97ec5d4b5c Return element IDs can now be set when popping the dialog stack 2019-02-20 10:30:41 +02:00
Stefan Zermatten
6c421c3a98 Smart inputs now run all their debounced functions before they get destroyed 2019-02-20 10:30:07 +02:00
Stefan Zermatten
665a9d716a Unset modifiers of attributes that aren't abilities 2019-02-20 09:27:37 +02:00
Stefan Zermatten
aa7a426e9f Fixed some issues with computation 2019-02-19 11:12:46 +02:00
Stefan Zermatten
da6909a997 Added skill dialog 2019-02-19 11:12:37 +02:00
Stefan Zermatten
3129b86ef0 Improved action and attack schemas 2019-02-18 15:52:17 +02:00
Stefan Zermatten
e63ae96cb5 Changed dialog stack from using element ids to data-ids to allow duplicate ids to work 2019-02-18 15:17:31 +02:00
Stefan Zermatten
a31a70f435 Made health bar editing compatible with dark mode 2019-02-18 14:26:46 +02:00
Stefan Zermatten
c5899e0816 Improved dialog stack handling of scrolling while the stack is open 2019-02-18 13:41:36 +02:00
Stefan Zermatten
d30ee06e33 Added edit tab to base dialogs, added edit screen to attribute dialog 2019-02-18 13:41:21 +02:00
Stefan Zermatten
5d45788521 Fixed a bug with smart inputs getting stuck loading if you set them to their original value and tabbed away 2019-02-15 14:50:08 +02:00
Stefan Zermatten
23cd5d6a12 Added effect edit expansion panel 2019-02-15 14:25:11 +02:00
Stefan Zermatten
38da95256b Made icon search compatible with dark mode 2019-02-15 12:48:24 +02:00
Stefan Zermatten
f773a20a59 Made hit dice list tiles compatible with dark mode 2019-02-15 12:24:52 +02:00
Stefan Zermatten
23654cd09c Made effect edit compatible with dark mode 2019-02-15 12:21:02 +02:00
Stefan Zermatten
09b2c38b43 Made color picker compatible with dark mode 2019-02-15 12:17:12 +02:00
Stefan Zermatten
1103248574 Abstracted effect list tile into its own component and made it dark mode compatible 2019-02-15 12:03:57 +02:00
Stefan Zermatten
96bb072dad Made health bar compatible with dark mode 2019-02-15 11:46:25 +02:00
Stefan Zermatten
3f6b3fe4c1 Made ability list tiles compatible with dark mode 2019-02-13 15:47:25 +02:00
Stefan Zermatten
ad7a485778 Added dark mode 2019-02-13 14:44:25 +02:00
Stefan Zermatten
58949e14fe Added a tree view of the character, fixed the issue it revealed 2019-02-11 16:15:57 +02:00
Stefan Zermatten
8bcb4407e6 Added method to persist hit dice arrow buttons to database 2019-02-11 13:55:43 +02:00
Stefan Zermatten
fd15e8a6c7 Removed duplicate hit dice effects 2019-02-11 13:53:11 +02:00
Stefan Zermatten
938573a4b4 Improved attribute dialogs 2019-02-07 15:36:47 +02:00
Stefan Zermatten
465f6645b7 Fixed colors of character sheet and base dialog toolbars 2019-02-07 13:56:48 +02:00
Stefan Zermatten
4b25373c7c Abstracted text fields into smart input components 2019-02-07 13:53:44 +02:00
Stefan Zermatten
5142b8e1a0 Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud1 into version-2 2019-02-07 10:02:50 +02:00
Thaum Rystra
f8e83ebe7e Added text-field component to edit database text without it getting bashed by data cleaning 2019-02-06 19:38:27 +02:00
Stefan Zermatten
b67926e0fc Added schema defaults to all schemas to prevent strings from being trimmed 2019-02-06 17:32:08 +02:00
Stefan Zermatten
bf2e9439cf Added debouncing for ui update functions 2019-02-06 17:26:56 +02:00
Stefan Zermatten
de4509ab6a made reset options clearable 2019-02-06 15:28:23 +02:00
Stefan Zermatten
f42f9590d2 Updated packages 2019-02-06 15:28:02 +02:00
Stefan Zermatten
29c5ed2194 Improved select menus 2019-02-06 14:46:21 +02:00
Stefan Zermatten
cf0440a8db Added color picker 2019-02-06 13:43:11 +02:00
Stefan Zermatten
4917729f29 Added basic attribute edit form 2019-02-06 11:16:32 +02:00
Stefan Zermatten
fe8e72d225 Changed primary color to red, since form elements default to it, secondary color is now grey 2019-02-06 10:57:47 +02:00
Stefan Zermatten
0e913b1f63 Items now have a number of uses which can be used up. 2019-02-06 10:08:22 +02:00
Stefan Zermatten
58f26cf849 Added sensible defaults to character creation dialog 2019-02-04 14:49:28 +02:00
Stefan Zermatten
836b10d499 Fixed some bugs in creature computations, removed debugging logs 2019-02-04 14:49:14 +02:00
Stefan Zermatten
8d4146f242 Added constitution bonus to hitpoints as a default effect 2019-02-04 14:48:25 +02:00
Stefan Zermatten
7742e5deef Gave defaults to adjustments and effects for attributes in case they have not been set yet 2019-02-04 14:30:41 +02:00
Stefan Zermatten
41c69ae040 Allowed character creation dialog to be cancelled 2019-02-04 14:30:17 +02:00
Stefan Zermatten
c6e7f8eeb6 Added effect editing component, abstracted out operation icons 2019-02-04 14:30:04 +02:00
Stefan Zermatten
e3e7b76f02 Results of effects are now stored on the effect, fixed defaults to suit 2019-02-04 14:29:03 +02:00
Stefan Zermatten
2dcbc91ccd Removed value from effects, calculation now stores the effect value even if it's a number 2019-02-04 14:28:21 +02:00
Stefan Zermatten
2fcf3047ee made sure healthBar elements that will be animated to have solid backgrounds 2019-01-31 10:34:50 +02:00
Stefan Zermatten
a95ae88a31 Fixed dialog animations to elements with no box shadow 2019-01-31 10:34:17 +02:00
Stefan Zermatten
f6aca08f63 Added missing import 2019-01-31 10:33:59 +02:00
Stefan Zermatten
87ee39b5bd Updated Readme 2019-01-31 10:08:01 +02:00
Stefan Zermatten
d466bfe428 Allowed skills to come with a base proficiency 2019-01-31 09:27:27 +02:00
Stefan Zermatten
e357b29145 Added labels to healthBars and improved attribute dialog opening 2019-01-30 16:38:47 +02:00
Stefan Zermatten
a80d070533 Refactored ability dialogs as generalised attribute dialogs 2019-01-30 14:10:46 +02:00
Stefan Zermatten
80d369f0d4 Got healthbars persisting data to the database 2019-01-30 13:34:45 +02:00
Stefan Zermatten
f6b0c746cc Improved ability dialogs 2019-01-29 16:36:13 +02:00
Stefan Zermatten
4584499019 Added A way to get Game-icons.net icons into the database (it's not secure yet), plus some icon ui 2019-01-28 16:26:39 +02:00
Stefan Zermatten
77d2f87373 Fixed an issue caused by storing components on the store, added ability dialog 2019-01-24 16:45:02 +02:00
Stefan Zermatten
8c0edfaa93 Added separate return element ids to dialogs 2019-01-24 15:11:42 +02:00
Stefan Zermatten
2e6ef52594 Dialog stack animations complete 2019-01-24 14:40:38 +02:00
Stefan Zermatten
00e8cbc1c8 Added dialog animations, still working on box shadows 2019-01-23 16:49:58 +02:00
Stefan Zermatten
e8a0e86548 Added creature view and edit permissions 2019-01-23 16:49:47 +02:00
Stefan Zermatten
60dfba3b46 Completed the stats tab, conditions not added yet 2019-01-21 16:03:05 +02:00
Stefan Zermatten
e43718f034 Removed outdated comment 2019-01-18 13:27:56 +02:00
Stefan Zermatten
36022e4bc4 Added hit dice tiles 2019-01-18 13:00:44 +02:00
Stefan Zermatten
0497223804 Added head request for roboto so that all font weights work 2019-01-18 12:54:40 +02:00
Stefan Zermatten
ef9512f0ec Moved storybook to route of UI 2019-01-18 12:18:16 +02:00
Stefan Zermatten
369dae17ee Storybook now has the option not to wrap a story in a card 2019-01-18 11:34:16 +02:00
Stefan Zermatten
4728d06c0b Added attribute cards 2019-01-18 10:38:28 +02:00
Stefan Zermatten
2d3cb367da Added ability score list tiles 2019-01-17 16:37:37 +02:00
Stefan Zermatten
f2137e26b2 Removed the space between a skill name and the conditional benefit astrisk 2019-01-17 15:35:16 +02:00
Stefan Zermatten
0bd654e557 Gave health bars a background fade so that you can click away from them to close the edit box 2019-01-17 15:26:07 +02:00
Stefan Zermatten
f06c4adb32 Corrrected the full proficiency icon 2019-01-17 15:07:12 +02:00
Stefan Zermatten
fabf377f72 Added Skill list tiles 2019-01-17 15:01:45 +02:00
Stefan Zermatten
03c244e7c9 Added health bar story and improved health bar functionality 2019-01-17 15:01:34 +02:00
Stefan Zermatten
ea7eabf27d Updated packages and meteor version, now need NO_HMR=1 when running in dev mode 2019-01-17 15:01:16 +02:00
Stefan Zermatten
bbbed216eb Added A dev-only storybook 2019-01-17 15:00:44 +02:00
Stefan Zermatten
c5cb98dc9f Added basic health bar with editing popup 2019-01-09 14:40:44 +02:00
Stefan Zermatten
aa6d973ae1 Character sheet toolbars now take on their character's color 2019-01-09 14:40:31 +02:00
Stefan Zermatten
76da2c8393 Added Character sheet 2018-12-21 12:17:49 +02:00
Stefan Zermatten
e0195499e5 Fixed broken sidebar links 2018-12-21 10:51:33 +02:00
Stefan Zermatten
798cf3edd7 Characters now insert with intelligent defaults based on the character wizard 2018-12-19 14:15:56 +02:00
Stefan Zermatten
13669fdc91 Refactored character insertion and continued work on creation dialog 2018-12-18 14:20:17 +02:00
Stefan Zermatten
dcc460d9e6 Migrations 2018-11-28 11:48:49 +02:00
Stefan Zermatten
c7fcb4de0c More migrations... 2018-11-15 15:22:20 +02:00
Stefan Zermatten
a5e7bd95c7 Indexed publications and imported them when the server starts 2018-11-15 11:23:57 +02:00
Stefan Zermatten
51919297df moved publications to imports folder 2018-11-15 11:20:49 +02:00
Stefan Zermatten
5b80032fa1 Migrated publications to import format 2018-11-15 11:20:25 +02:00
Stefan Zermatten
ade614dd5f Finished migrating creatures to simpl-schema 2018-11-15 11:11:26 +02:00
Stefan Zermatten
c4984c07bf More progress on migrating schema 2018-11-12 16:23:43 +02:00
Stefan Zermatten
b49ab67390 Removed .idea folder 2018-10-29 12:28:59 +02:00
Stefan Zermatten
c7f0c0a4c4 Removed old polymer components 2018-10-29 12:28:33 +02:00
Stefan Zermatten
7cda854d22 Continued migrating to Simpl-Schema and imports 2018-10-19 14:01:23 +02:00
Stefan Zermatten
989706483a updated CustomBuffs 2018-10-12 13:44:36 +02:00
Stefan Zermatten
53a1137848 Updated simpl-schema and collection2, started untangling the mess that made 2018-10-12 13:38:51 +02:00
Stefan Zermatten
e3065f089f Moved effects to default character stats 2018-10-12 12:38:24 +02:00
Stefan Zermatten
6aaa4ebe00 Added base values to skills to make implementing non player creatures easier 2018-10-12 12:35:15 +02:00
Stefan Zermatten
158615c25c moved exports to the function definitions, rather than bottom of file 2018-10-12 12:24:56 +02:00
Stefan Zermatten
4cd46fe209 Refactored creature computation with more comments 2018-10-12 12:14:15 +02:00
Stefan Zermatten
04059709eb Got creature computation working again after moving to imports dir 2018-10-12 11:59:29 +02:00
Stefan Zermatten
d117570165 renamed model to api 2018-10-12 09:28:16 +02:00
Stefan Zermatten
4b900d5664 lowercased all model directories 2018-10-12 09:21:03 +02:00
Stefan Zermatten
189a1d0a16 Started work on migrating model to lazy evaluation 2018-10-12 09:18:18 +02:00
Stefan Zermatten
ca8223ccad Moved the model into the imports directory 2018-10-12 09:01:23 +02:00
Stefan Zermatten
b2eed9a672 Renamed Characters to Creatures 2018-10-12 08:52:14 +02:00
Stefan Zermatten
d330e15dce Added character creation dialog 2018-10-11 16:39:55 +02:00
Stefan Zermatten
4ac56a31de Successful registration pushes the user to the character list 2018-10-11 11:50:19 +02:00
Stefan Zermatten
41a25151fc Rolled back 1.8 update because it broke the build 2018-10-11 11:39:27 +02:00
Stefan Zermatten
e1c1b727ed updated Meteor to 1.8 2018-10-11 10:44:07 +02:00
Stefan Zermatten
dc8185df98 Got basic dialog working, no morph animation yet 2018-10-09 16:24:43 +02:00
Stefan Zermatten
89820780b5 Added accounts page 2018-10-05 12:55:21 +02:00
Stefan Zermatten
d0ce162315 Made sure some links use vue router 2018-10-03 16:37:12 +02:00
Stefan Zermatten
6835f5f4f9 Removed blaze, old client side code 2018-10-03 11:14:23 +02:00
Stefan Zermatten
e9d5e85e75 Started on dialog stack 2018-10-02 08:51:04 +02:00
Stefan Zermatten
c46f8c5171 Speed dial fixed 2018-10-02 08:50:45 +02:00
Thaum Rystra
f41ff1c52f Started work on vue sign-in 2018-10-01 09:38:03 +02:00
Stefan Zermatten
7f418c26da Started work on character list page 2018-09-28 13:07:32 +02:00
Stefan Zermatten
acdc084905 Home and sidebar implemented 2018-09-28 13:07:12 +02:00
Stefan Zermatten
6a73d3576a Added Vue, looks like this branch is now going to be DiceCloud V2 2018-09-27 14:55:27 +02:00
Stefan Zermatten
d82031ab0d Added indices, fixed bugs 2018-09-27 09:39:21 +02:00
Stefan Zermatten
cf33bfcce1 Fixed compile errors 2018-09-25 16:04:30 +02:00
Stefan Zermatten
005433268d Added migration code 2018-09-25 15:23:52 +02:00
Stefan Zermatten
ae470642c1 Computed characters are now written back to the database 2018-09-25 11:51:06 +02:00
Stefan Zermatten
dd89556b7f Moved recompute character XP and Weight Carried around 2018-09-25 11:10:47 +02:00
Stefan Zermatten
b3c2176de8 Added weight carried recomputation 2018-09-25 09:07:43 +02:00
Stefan Zermatten
9fccc3be61 Renamed addDefaultAttributes to addDefaultStats 2018-08-28 09:57:09 +02:00
Stefan Zermatten
bc26a77f92 Merge branch 'feature-server-computation' of https://github.com/ThaumRystra/DiceCloud1 into feature-server-computation 2018-08-28 09:55:26 +02:00
Stefan Zermatten
fa3cca7193 Added baseValue to attributes 2018-08-28 09:55:22 +02:00
Thaum Rystra
662fc91e17 New characters get the appropriate attributes/skills/damageMultipliers 2018-08-25 20:19:17 +02:00
Stefan Zermatten
b4506fd939 Began abstracting default abilities, damage multipliers and skills into rulesets. 2018-08-24 13:30:31 +02:00
Stefan Zermatten
5c40a84660 Changed Characters to creatures, as the new attributes system is generalisable 2018-08-24 12:14:03 +02:00
Stefan Zermatten
7c9687955d Added Models for attributes, skills and damage multipliers 2018-08-24 12:12:38 +02:00
Stefan Zermatten
b21a91b0aa Added very basic testing, got attribute calculations working 2018-08-24 10:58:59 +02:00
Stefan Zermatten
f4b1da0c80 Comments and some refactoring 2018-08-23 16:17:42 +02:00
Stefan Zermatten
755e7fba30 Started the big move to server-side computation. 2018-08-23 15:55:21 +02:00
701 changed files with 21883 additions and 23130 deletions

View File

@@ -1,13 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="false" level="WARNING" enabled_by_default="false">
<option name="myValues">
<value>
<list size="0" />
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</settings>
</component>

56
.jscsrc
View File

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

View File

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

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM ubuntu:latest
RUN apt-get update --quiet \
&& apt-get install --quiet --yes \
bsdtar \
curl \
git
RUN ln --symbolic --force $(which bsdtar) $(which tar)
RUN useradd --create-home --shell /bin/bash dicecloud
USER dicecloud
WORKDIR /home/dicecloud
RUN curl https://install.meteor.com/?release=1.8.0.2 | sh
ENV PATH="${PATH}:/home/dicecloud/.meteor"
COPY dev.sh ./dev.sh
ENTRYPOINT ./dev.sh

View File

@@ -1,13 +1,70 @@
RPG Docs
DiceCloud
========
This is the repo for [DiceCloud](dicecloud.com).
DiceCloud is a free, auditable, real-time character sheet for D&D 5e.
Philosophy
----------
Setting up your character on DiceCloud takes a little longer than
just filling it in on a paper character sheet would. The goal of using an
online sheet is to make actually playing the game more streamlined, and
ultimately more fun. So putting a little extra effort into setting up a
character now pays off over and over again once you're playing.
The idea is to track where each number comes from, and allow you to easily make
changes on the fly. Let's look at a hypothetical example.
> You need to swim through a sunken section of dungeon to fetch the quest's Thing.
> You'll need to take off your magical Plate Armor of +1 Constitution to swim
> without sinking, of course.
>
> Taking it off will take away that disadvantage on
> stealth checks, change your armor class, your speed and your constitution, and
> which in turn changes your hit points and your constitution saving throw.
> Working out all those changes in the middle of a game will drag the game to a
> halt.
>
> Fortunately you have DiceCloud, so it's a matter of dragging
> your Plate Armor +1 Con from your "equipment" box to your "backpack" box and
> you're done. Your hitpoints change correctly, your saving throws are up to date,
> your armor class goes back to reflecting the fact that you have natural armor
> from being a dragonborn. Your character sheet keeps up and you
> ultimately get more time to play the game. Huzzah!
Getting started
---------------
`git clone https://github.com/ThaumRystra/DiceCloud1 dicecloud`
Running DiceCloud locally, either to host it yourself away from an internet
connection, or to contribute to developing it further, is fairly
straightforward and it should work on Linux, Windows, and Mac.
You'll need to have installed:
- [git](https://www.atlassian.com/git/tutorials/install-git)
- [Meteor](https://www.meteor.com/install)
Then, it's just a matter of cloning this repository into a folder, and running
`meteor` in the app directory.
`git clone https://github.com/ThaumRystra/DiceCloud dicecloud`
`cd dicecloud`
`cd app`
`bower install`
`meteor npm install`
`meteor`
You should see this:
```
=> Started proxy.
=> [HMR] Dev server listening on port 3003.
=> Started MongoDB.
=> Started your app.
=> App running at: http://localhost:3000/
```
Now, visiting [](http://localhost:3000/) should show you an empty instance of
DiceCloud running.

View File

@@ -1 +0,0 @@
{"directory":"public/components/"}

2
app/.gitignore vendored
View File

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

View File

@@ -16,3 +16,4 @@ notices-for-facebook-graph-api-2
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base
1.8.3-split-jquery-from-blaze

View File

@@ -3,54 +3,49 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
accounts-password@1.5.1
accounts-ui@1.3.0
random@1.1.0
accounts-password@1.6.0
accounts-ui@1.3.1
random@1.2.0
dburles:collection-helpers
reactive-var@1.0.11
underscore@1.0.10
aldeed:collection2
matb33:collection-hooks
zimme:collection-softremovable
momentjs:moment
dburles:mongo-collection-instances
percolate:migrations
ecwyne:mathjs
useraccounts:polymer
accounts-google@1.3.1
splendido:accounts-meld
accounts-google@1.3.3
email@1.2.3
meteorhacks:subs-manager
chuangbo:marked
reywood:iron-router-ga
meteor-base@1.4.0
mobile-experience@1.0.5
mongo@1.5.0
blaze-html-templates
session@1.1.7
mobile-experience@1.1.0
mongo@1.10.0
session@1.2.0
jquery@1.11.10
tracker@1.2.0
logging@1.1.20
reload@1.2.0
ejson@1.1.0
spacebars
reload@1.3.0
ejson@1.1.1
check@1.3.1
useraccounts:iron-routing
wizonesolutions:canonical
standard-minifier-js@2.3.4
shell-server@0.3.1
standard-minifier-js@2.6.0
shell-server@0.5.0
seba:minifiers-autoprefixer
nikogosovd:multiple-uihooks
templates:array
ecmascript@0.11.1
ecmascript@0.14.3
es5-shim@4.8.0
differential:vulcanize
reactive-dict@1.2.0
reactive-dict@1.3.0
percolate:synced-cron
ongoworks:speakingurl
service-configuration@1.0.11
google-config-ui@1.0.0
dynamic-import@0.4.0
google-config-ui@1.0.1
dynamic-import@0.5.2
ddp-rate-limiter@1.0.7
rate-limit@1.0.9
iron:router
meteortesting:mocha
mdg:validated-method
akryum:vue-router2
static-html
aldeed:collection2@3.0.0
aldeed:schema-index
akryum:vue-component
accounts-patreon
bozhao:link-accounts

View File

@@ -1 +1 @@
METEOR@1.7.0.3
METEOR@1.10.2

View File

@@ -1,136 +1,126 @@
accounts-base@1.4.2
accounts-google@1.3.1
accounts-oauth@1.1.15
accounts-password@1.5.1
accounts-ui@1.3.0
accounts-ui-unstyled@1.4.1
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.4
accounts-base@1.6.0
accounts-google@1.3.3
accounts-oauth@1.2.0
accounts-password@1.6.0
accounts-patreon@0.1.0
accounts-ui@1.3.1
accounts-ui-unstyled@1.4.2
akryum:npm-check@0.1.2
akryum:vue-component@0.15.2
akryum:vue-component-dev-client@0.4.7
akryum:vue-component-dev-server@0.1.4
akryum:vue-router2@0.2.3
aldeed:collection2@3.0.6
aldeed:schema-index@3.0.0
allow-deny@1.1.0
autoupdate@1.4.1
babel-compiler@7.1.1
babel-runtime@1.2.2
base64@1.0.11
binary-heap@1.0.10
blaze@2.3.2
blaze-html-templates@1.1.2
autoupdate@1.6.0
babel-compiler@7.5.3
babel-runtime@1.5.0
base64@1.0.12
binary-heap@1.0.11
blaze@2.3.4
blaze-tools@1.0.10
boilerplate-generator@1.5.0
caching-compiler@1.1.12
boilerplate-generator@1.7.0
bozhao:link-accounts@2.1.1
caching-compiler@1.2.2
caching-html-compiler@1.1.3
callback-hook@1.1.0
callback-hook@1.3.0
check@1.3.1
chuangbo:marked@0.3.5_1
coffeescript@1.0.17
dburles:collection-helpers@1.1.0
dburles:mongo-collection-instances@0.3.5
ddp@1.4.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-rate-limiter@1.0.7
ddp-server@2.2.0
ddp-server@2.3.1
deps@1.0.12
diff-sequence@1.1.0
differential:vulcanize@3.0.0
dynamic-import@0.4.1
ecmascript@0.11.1
diff-sequence@1.1.1
dynamic-import@0.5.2
ecmascript@0.14.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.7.1
ecmascript-runtime-server@0.7.0
ecwyne:mathjs@0.25.0
ejson@1.1.0
ecmascript-runtime-client@0.10.0
ecmascript-runtime-server@0.9.0
ejson@1.1.1
email@1.2.3
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
google-config-ui@1.0.0
google-oauth@1.2.5
google-config-ui@1.0.1
google-oauth@1.3.0
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
http@1.4.1
http@1.4.2
id-map@1.1.0
iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.1.0
iron:router@1.1.2
iron:url@1.1.0
inter-process-messaging@0.1.1
jquery@1.11.11
lai:collection-extensions@0.2.1_1
launch-screen@1.1.1
less@2.7.12
launch-screen@1.2.0
less@2.8.0
livedata@1.0.18
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.1.20
matb33:collection-hooks@0.8.4
mdg:validation-error@0.5.1
meteor@1.9.2
mdg:validated-method@1.2.0
meteor@1.9.3
meteor-base@1.4.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
minifier-css@1.3.1
minifier-js@2.3.5
minimongo@1.4.4
mobile-experience@1.0.5
mobile-status-bar@1.0.14
modern-browsers@0.1.2
modules@0.12.2
modules-runtime@0.10.2
momentjs:moment@2.22.2
mongo@1.5.1
meteortesting:browser-tests@1.3.3
meteortesting:mocha@1.1.5
meteortesting:mocha-core@7.0.1
minifier-css@1.5.0
minifier-js@2.6.0
minimongo@1.6.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.5
modules@0.15.0
modules-runtime@0.12.0
momentjs:moment@2.24.0
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
nikogosovd:multiple-uihooks@0.1.8
npm-bcrypt@0.9.3
npm-mongo@3.0.11
oauth@1.2.3
oauth2@1.2.0
npm-mongo@3.7.0
oauth@1.3.0
oauth2@1.3.0
observe-sequence@1.0.16
ongoworks:speakingurl@9.0.0
ordered-dict@1.1.0
patreon-oauth@0.1.0
percolate:migrations@0.9.8
percolate:synced-cron@1.3.2
promise@0.11.1
promise@0.11.2
raix:eventemitter@0.1.3
random@1.1.0
random@1.2.0
rate-limit@1.0.9
reactive-dict@1.2.0
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.2.0
reload@1.3.0
retry@1.1.0
reywood:iron-router-ga@0.7.1
routepolicy@1.0.13
seba:minifiers-autoprefixer@1.0.1
routepolicy@1.1.0
seba:minifiers-autoprefixer@1.1.2
service-configuration@1.0.11
session@1.1.7
session@1.2.0
sha@1.0.9
shell-server@0.3.1
socket-stream-client@0.2.2
softwarerero:accounts-t9n@1.3.11
shell-server@0.5.0
socket-stream-client@0.3.0
spacebars@1.0.15
spacebars-compiler@1.1.3
splendido:accounts-emails-field@1.2.0
splendido:accounts-meld@1.3.1
srp@1.0.10
standard-minifier-js@2.3.4
srp@1.1.0
standard-minifier-js@2.6.0
static-html@1.2.2
templates:array@1.0.3
templating@1.3.2
templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tmeasday:check-npm-versions@0.3.2
tracker@1.2.0
ui@1.0.13
underscore@1.0.10
url@1.2.0
useraccounts:core@1.14.2
useraccounts:iron-routing@1.14.2
useraccounts:polymer@1.14.2
webapp@1.6.2
url@1.3.0
webapp@1.9.1
webapp-hashing@1.0.9
wizonesolutions:canonical@0.0.5
zimme:collection-behaviours@1.1.3
zimme:collection-softremovable@1.0.5

View File

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

View File

@@ -1,40 +0,0 @@
Actions = new Mongo.Collection("actions");
/*
* Actions are given to a character by items and features
*/
Schemas.Action = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
type: {
type: String,
allowedValues: ["action, bonus, reaction, free"],
defaultValue: "action",
},
//the immediate impact of doing this action (eg. -1 rages)
adjustments: {
type: [Schemas.Adjustment],
defaultValue: [],
},
});
Actions.attachSchema(Schemas.Action);
Actions.attachBehaviour("softRemovable");
makeChild(Actions);
Actions.allow(CHARACTER_SUBSCHEMA_ALLOW);
Actions.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,87 +0,0 @@
Attacks = new Mongo.Collection("attacks");
/*
* Attacks are given to a character by items and features
*/
Schemas.Attack = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
defaultValue: "New Attack",
optional: true,
trim: false,
},
details: {
type: String,
optional: true,
trim: false,
},
attackBonus: {
type: String,
defaultValue: "strengthMod + proficiencyBonus",
optional: true,
trim: false,
},
damage: {
type: String,
defaultValue: "1d8 + {strengthMod}",
optional: true,
trim: false,
},
damageType: {
type: String,
allowedValues: [
"bludgeoning",
"piercing",
"slashing",
"acid",
"cold",
"fire",
"force",
"lightning",
"necrotic",
"poison",
"psychic",
"radiant",
"thunder",
],
defaultValue: "slashing",
},
//the id of the feature, buff or item that created this effect
parent: {
type: Schemas.Parent
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
enabled: {
type: Boolean,
defaultValue: true,
},
});
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,67 +0,0 @@
Buffs = new Mongo.Collection("buffs");
Schemas.Buff = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
enabled: {
type: Boolean,
defaultValue: true,
},
type: {
type: String,
allowedValues: [
"inate", //this should be "innate", but changing it could be problematic
"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",
},
appliedBy: { //the charId of whoever applied the buff
type: String,
regEx: SimpleSchema.RegEx.Id,
},
appliedByDetails: {//the name and collection of the thing that applied the buff
type: Object,
optional: true,
},
"appliedByDetails.name": {
type: String,
},
"appliedByDetails.collection": {
type: String,
},
});
Buffs.attachSchema(Schemas.Buff);
Buffs.attachBehaviour("softRemovable");
makeParent(Buffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,589 +0,0 @@
//set up the collection for characters
Characters = new Mongo.Collection("characters");
Schemas.Character = new SimpleSchema({
//strings
name: {type: String, defaultValue: "", trim: false, optional: true},
urlName: {type: String, defaultValue: "-", trim: false, optional: true},
alignment: {type: String, defaultValue: "", trim: false, optional: true},
gender: {type: String, defaultValue: "", trim: false, optional: true},
race: {type: String, defaultValue: "", trim: false, optional: true},
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
strength: {type: Schemas.Attribute},
dexterity: {type: Schemas.Attribute},
constitution: {type: Schemas.Attribute},
intelligence: {type: Schemas.Attribute},
wisdom: {type: Schemas.Attribute},
charisma: {type: Schemas.Attribute},
//stats
hitPoints: {type: Schemas.Attribute},
tempHP: {type: Schemas.Attribute},
experience: {type: Schemas.Attribute},
proficiencyBonus: {type: Schemas.Attribute},
speed: {type: Schemas.Attribute},
weight: {type: Schemas.Attribute},
age: {type: Schemas.Attribute},
ageRate: {type: Schemas.Attribute},
armor: {type: Schemas.Attribute},
carryMultiplier: {type: Schemas.Attribute},
//resources
level1SpellSlots: {type: Schemas.Attribute},
level2SpellSlots: {type: Schemas.Attribute},
level3SpellSlots: {type: Schemas.Attribute},
level4SpellSlots: {type: Schemas.Attribute},
level5SpellSlots: {type: Schemas.Attribute},
level6SpellSlots: {type: Schemas.Attribute},
level7SpellSlots: {type: Schemas.Attribute},
level8SpellSlots: {type: Schemas.Attribute},
level9SpellSlots: {type: Schemas.Attribute},
ki: {type: Schemas.Attribute},
sorceryPoints: {type: Schemas.Attribute},
rages: {type: Schemas.Attribute},
superiorityDice: {type: Schemas.Attribute},
expertiseDice: {type: Schemas.Attribute},
//specific features
rageDamage: {type: Schemas.Attribute},
//hit dice
d6HitDice: {type: Schemas.Attribute},
d8HitDice: {type: Schemas.Attribute},
d10HitDice: {type: Schemas.Attribute},
d12HitDice: {type: Schemas.Attribute},
//vulnerabilities
acidMultiplier: {type: Schemas.Attribute},
bludgeoningMultiplier: {type: Schemas.Attribute},
coldMultiplier: {type: Schemas.Attribute},
fireMultiplier: {type: Schemas.Attribute},
forceMultiplier: {type: Schemas.Attribute},
lightningMultiplier: {type: Schemas.Attribute},
necroticMultiplier: {type: Schemas.Attribute},
piercingMultiplier: {type: Schemas.Attribute},
poisonMultiplier: {type: Schemas.Attribute},
psychicMultiplier: {type: Schemas.Attribute},
radiantMultiplier: {type: Schemas.Attribute},
slashingMultiplier: {type: Schemas.Attribute},
thunderMultiplier: {type: Schemas.Attribute},
//skills
//saves
strengthSave: {type: Schemas.Skill},
"strengthSave.ability": {type: String, defaultValue: "strength"},
dexteritySave: {type: Schemas.Skill},
"dexteritySave.ability": {type: String, defaultValue: "dexterity"},
constitutionSave:{type: Schemas.Skill},
"constitutionSave.ability": {type: String, defaultValue: "constitution"},
intelligenceSave:{type: Schemas.Skill},
"intelligenceSave.ability": {type: String, defaultValue: "intelligence"},
wisdomSave: {type: Schemas.Skill},
"wisdomSave.ability": {type: String, defaultValue: "wisdom"},
charismaSave: {type: Schemas.Skill},
"charismaSave.ability": {type: String, defaultValue: "charisma"},
//skill skills
acrobatics: {type: Schemas.Skill},
"acrobatics.ability": {type: String, defaultValue: "dexterity"},
animalHandling: {type: Schemas.Skill},
"animalHandling.ability": {type: String, defaultValue: "wisdom"},
arcana: {type: Schemas.Skill},
"arcana.ability": {type: String, defaultValue: "intelligence"},
athletics: {type: Schemas.Skill},
"athletics.ability": {type: String, defaultValue: "strength"},
deception: {type: Schemas.Skill},
"deception.ability": {type: String, defaultValue: "charisma"},
history: {type: Schemas.Skill},
"history.ability": {type: String, defaultValue: "intelligence"},
insight: {type: Schemas.Skill},
"insight.ability": {type: String, defaultValue: "wisdom"},
intimidation: {type: Schemas.Skill},
"intimidation.ability": {type: String, defaultValue: "charisma"},
investigation: {type: Schemas.Skill},
"investigation.ability": {type: String, defaultValue: "intelligence"},
medicine: {type: Schemas.Skill},
"medicine.ability": {type: String, defaultValue: "wisdom"},
nature: {type: Schemas.Skill},
"nature.ability": {type: String, defaultValue: "intelligence"},
perception: {type: Schemas.Skill},
"perception.ability": {type: String, defaultValue: "wisdom"},
performance: {type: Schemas.Skill},
"performance.ability": {type: String, defaultValue: "charisma"},
persuasion: {type: Schemas.Skill},
"persuasion.ability": {type: String, defaultValue: "charisma"},
religion: {type: Schemas.Skill},
"religion.ability": {type: String, defaultValue: "intelligence"},
sleightOfHand: {type: Schemas.Skill},
"sleightOfHand.ability": {type: String, defaultValue: "dexterity"},
stealth: {type: Schemas.Skill},
"stealth.ability": {type: String, defaultValue: "dexterity"},
survival: {type: Schemas.Skill},
"survival.ability": {type: String, defaultValue: "wisdom"},
//Mechanical Skills
initiative: {type: Schemas.Skill},
"initiative.ability": {type: String, defaultValue: "dexterity"},
dexterityArmor: {type: Schemas.Skill},
"dexterityArmor.ability": {type: String, defaultValue: "dexterity"},
//mechanics
deathSave: {type: Schemas.DeathSave},
//permissions
party: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true},
owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
//TODO add per-character settings
//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"],
index: 1,
},
"settings.swapStatAndModifier": {type: Boolean, defaultValue: false},
"settings.exportFeatures": {type: Boolean, defaultValue: true},
"settings.exportAttacks": {type: Boolean, defaultValue: true},
"settings.exportDescription": {type: Boolean, defaultValue: true},
"settings.newUserExperience": {type: Boolean, optional: true},
});
Characters.attachSchema(Schemas.Character);
var attributeBase = preventLoop(function(charId, statName){
check(statName, String);
//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;
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;
}
});
var result = (base + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
// Don't round carry multiplier
if (statName === "carryMultiplier"){
return result;
}
return Math.floor(result);
});
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;
}
}
});
}
//create a local memoize with a argument concatenating hash function
var memoize = function(f) {
if (Meteor.isServer) return f;
return Tracker.memoize(f, function() {
return _.reduce(arguments, function(memo, arg) {
return memo + arg;
}, "");
});
};
//memoize funcitons that have finds and slow loops
Characters.calculate = {
getField: function(charId, fieldName) {
var fieldSelector = {};
fieldSelector[fieldName] = 1;
var char = Characters.findOne(charId, {fields: fieldSelector});
if (!char) return;
var field = char[fieldName];
if (field === undefined){
throw new Meteor.Error(
"getField failed",
"getField could not find field " +
fieldName +
" in character " +
char._id
);
}
return field;
},
fieldValue: function(charId, fieldName) {
if (!Schemas.Character.schema(fieldName)){
throw new Meteor.Error(
"Field not found",
"Character's schema does not contain a field called: " + fieldName
);
}
//duck typing to get the right value function
//.ability implies skill
if (Schemas.Character.schema(fieldName + ".ability")){
return Characters.calculate.skillMod(charId, fieldName);
}
//adjustment implies attribute
if (Schemas.Character.schema(fieldName + ".adjustment")){
return Characters.calculate.attributeValue(charId, fieldName);
}
//fall back to just returning the field itself
return Characters.calculate.getField(charId, fieldName);
},
attributeValue: memoize(function(charId, attributeName){
var attribute = Characters.calculate.getField(charId, attributeName);
if (!attribute) return;
//base value
var value = Characters.calculate.attributeBase(charId, attributeName);
//plus adjustment
value += attribute.adjustment;
return value;
}),
attributeBase: memoize(function(charId, attributeName){
return attributeBase(charId, attributeName);
}),
skillMod: memoize(preventLoop(function(charId, skillName){
var skill = Characters.calculate.getField(charId, skillName);
if (!skill) return;
//get the final value of the ability score
var ability = Characters.calculate.attributeValue(charId, skill.ability);
//base modifier
var mod = +getMod(ability);
//multiply proficiency bonus by largest value in proficiency array
var prof = Characters.calculate.proficiency(charId, skillName);
//add multiplied proficiency bonus to modifier
mod += prof * Characters.calculate.attributeValue(charId, "proficiencyBonus");
//apply all effects
var value;
var add = 0;
var mul = 1;
var min = Number.NEGATIVE_INFINITY;
var max = Number.POSITIVE_INFINITY;
Effects.find({
charId: charId,
stat: skillName,
enabled: true,
operation: {$in: ["base", "add", "mul", "min", "max"]},
}).forEach(function(effect) {
value = evaluateEffect(charId, effect);
if (effect.operation === "add"){
add += value;
} else if (effect.operation === "mul"){
mul *= value;
} else if (effect.operation === "min"){
if (value > min) min = value;
} else if (effect.operation === "max"){
if (value < max) max = value;
}
});
var result = (mod + add) * mul;
if (result < min) result = min;
if (result > max) result = max;
return Math.floor(result);
})),
proficiency: memoize(function(charId, skillName){
//return largest value in proficiency array
var prof = Proficiencies.findOne(
{charId: charId, name: skillName, enabled: true},
{sort: {value: -1}}
);
return prof && prof.value || 0;
}),
passiveSkill: memoize(function(charId, skillName){
var mod = +Characters.calculate.skillMod(charId, skillName);
var value = 10 + mod;
Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "passiveAdd"}
).forEach(function(effect){
value += evaluateEffect(charId, effect);
});
var advantage = Characters.calculate.advantage(charId, skillName);
value += 5 * advantage;
return Math.floor(value);
}),
advantage: memoize(function(charId, skillName){
var advantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "advantage"}
).count();
var disadvantage = Effects.find(
{charId: charId, stat: skillName, enabled: true, operation: "disadvantage"}
).count();
if (advantage && !disadvantage) return 1;
if (disadvantage && !advantage) return -1;
return 0;
}),
abilityMod: function(charId, attribute){
return getMod(
Characters.calculate.attributeValue(charId, attribute)
);
},
passiveAbility: function(charId, attribute){
var mod = +getMod(Characters.calculate.attributeValue(charId, attribute));
return 10 + mod;
},
xpLevel: function(charId){
var xp = Characters.calculate.experience(charId);
for (var i = 0; i < 19; i++){
if (xp < XP_TABLE[i]){
return i;
}
}
if (xp > 355000) return 20;
return 0;
},
level: memoize(function(charId){
var level = 0;
Classes.find({charId: charId}).forEach(function(cls){
level += cls.level;
});
return level;
}),
experience: memoize(function(charId){
var xp = 0;
Experiences.find(
{charId: charId},
{fields: {value: 1}}
).forEach(function(e){
xp += e.value;
});
return xp;
}),
};
var deprecated = function() {
//var err = new Error("this function has been deprecated");
var name = "";
if (Template.instance()){
name = Template.instance().view.name;
}
var logString = "this function has been deprecated \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){
deprecated();
return Characters.calculate.getField(this._id, fieldName);
},
//returns the value of a field
fieldValue : function(fieldName){
deprecated();
return Characters.calculate.fieldValue(this._id, fieldName);
},
attributeValue: function(attributeName){
deprecated();
return Characters.calculate.attributeValue(this._id, attributeName);
},
attributeBase: function(attributeName){
deprecated();
return Characters.calculate.attributeBase(this._id, attributeName);
},
skillMod: function(skillName){
deprecated();
return Characters.calculate.skillMod(this._id, skillName);
},
proficiency: function(skillName){
deprecated();
return Characters.calculate.proficiency(this._id, skillName);
},
passiveSkill: function(skillName){
deprecated();
return Characters.calculate.passiveSkill(this._id, skillName);
},
advantage: function(skillName){
deprecated();
return Characters.calculate.advantage(this._id, skillName);
},
abilityMod: function(attribute){
deprecated();
return Characters.calculate.abilityMod(this._id, attribute);
},
passiveAbility: function(attribute){
deprecated();
return Characters.calculate.passiveAbility(this._id, attribute);
},
xpLevel: function(){
deprecated();
return Characters.calculate.xpLevel(this._id);
},
level: function(){
deprecated();
return Characters.calculate.level(this._id);
},
experience: function(){
deprecated();
return Characters.calculate.experience(this._id);
},
});
//clean up all data related to that character before removing it
if (Meteor.isServer){
Characters.after.remove(function(userId, character) {
Actions .remove({charId: character._id});
Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id});
Classes .remove({charId: character._id});
CustomBuffs .remove({charId: character._id});
Effects .remove({charId: character._id});
Experiences .remove({charId: character._id});
Features .remove({charId: character._id});
Notes .remove({charId: character._id});
Proficiencies .remove({charId: character._id});
SpellLists .remove({charId: character._id});
Items .remove({charId: character._id});
Containers .remove({charId: character._id});
});
Characters.after.update(function(userId, doc, fieldNames, modifier, options) {
if (_.contains(fieldNames, "name")){
var urlName = getSlug(doc.name, {maintainCase: true}) || "-";
Characters.update(doc._id, {$set: {urlName}});
}
});
Characters.before.insert(function(userId, doc) {
doc.urlName = getSlug(doc.name, {maintainCase: true}) || "-";
// The first character a user creates should have the new user experience
if (!Characters.find({owner: userId}).count()){
doc.settings.newUserExperience = true;
}
});
}
Characters.allow({
insert: function(userId, doc) {
// the user must be logged in, and the document must be owned by the user
return (userId && doc.owner === userId);
},
update: function(userId, doc, fields, modifier) {
// can only change documents you have write access to
return doc.owner === userId ||
_.contains(doc.writers, userId);
},
remove: function(userId, doc) {
// can only remove your own documents
return doc.owner === userId;
},
fetch: ["owner", "writers"],
});
Characters.deny({
update: function(userId, docs, fields, modifier) {
// can't change owners unless you are the current owner
return _.contains(fields, "owner") && doc.owner !== userId;
}
});

View File

@@ -1,32 +0,0 @@
Classes = new Mongo.Collection("classes");
Schemas.Class = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
level: {type: Number},
createdAt: {
type: Date,
autoValue: function() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return {$setOnInsert: new Date()};
} else {
this.unset();
}
},
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
});
Classes.attachSchema(Schemas.Class);
Classes.attachBehaviour("softRemovable");
makeParent(Classes, "name"); //parents of effects and attacks
Classes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Classes.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,42 +0,0 @@
Conditions = new Mongo.Collection("conditions");
Schemas.Conditions = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
"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",
},
});
Conditions.attachSchema(Schemas.Conditions);
Conditions.attachBehaviour("softRemovable");
makeParent(Conditions, ["name"]); //parents of effects, attacks, proficiencies
Conditions.allow(CHARACTER_SUBSCHEMA_ALLOW);
Conditions.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,53 +0,0 @@
CustomBuffs = new Mongo.Collection("customBuffs");
Schemas.CustomBuff = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true,
trim: false,
},
description: {
type: String,
optional: true,
trim: false,
},
target: {
type: String,
allowedValues: [
"self",
"others",
"both"
],
defaultValue: "self",
},
enabled: {
type: Boolean,
autoValue: function(){
return false;
//enabled is ALWAYS false on these, so that its children are also not enabled, so that the buff templates have no effects.
},
},
"lifeTime.total": {
type: Number,
defaultValue: 0, //0 is infinite
min: 0,
},
//the id of the feature, buff or item that creates this buff
parent: {
type: Schemas.Parent,
},
});
CustomBuffs.attachSchema(Schemas.CustomBuff);
CustomBuffs.attachBehaviour("softRemovable");
makeParent(CustomBuffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies. Since this represents a template, "enabled" is always false.
makeChild(CustomBuffs); //children of lots of things
CustomBuffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
CustomBuffs.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,132 +0,0 @@
Effects = new Mongo.Collection("effects");
/*
* Effects are reason-value attached to skills and abilities
* that modify their final value or presentation in some way
*/
Schemas.Effect = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
optional: true, //TODO make necessary if there is no owner
trim: false,
},
operation: {
type: String,
defaultValue: "add",
allowedValues: [
"base",
"proficiency",
"add",
"mul",
"min",
"max",
"advantage",
"disadvantage",
"passiveAdd",
"fail",
"conditional",
],
},
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
type: String,
optional: true,
trim: false,
},
//the thing that created this effect
parent: {
type: Schemas.Parent
},
//which stat the effect is applied to
stat: {
type: String,
optional: true,
},
enabled: {
type: Boolean,
defaultValue: true,
},
});
Effects.attachSchema(Schemas.Effect);
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,27 +0,0 @@
Experiences = new Mongo.Collection("experience");
Schemas.Experience = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false, defaultValue: "New Experience"},
description: {type: String, optional: true, trim: false},
value: {type: Number, defaultValue: 0},
dateAdded: {
type: Date,
autoValue: function() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return {$setOnInsert: new Date()};
} else {
this.unset();
}
},
},
});
Experiences.attachSchema(Schemas.Experience);
Experiences.attachBehaviour("softRemovable");
Experiences.allow(CHARACTER_SUBSCHEMA_ALLOW);
Experiences.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,115 +0,0 @@
Features = new Mongo.Collection("features");
Schemas.Feature = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
uses: {type: String, optional: true, trim: false},
used: {type: Number, defaultValue: 0},
reset: {
type: String,
allowedValues: ["manual", "longRest", "shortRest"],
defaultValue: "manual",
},
enabled: {type: Boolean, defaultValue: true},
alwaysEnabled:{type: Boolean, defaultValue: true},
color: {type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
});
Features.attachSchema(Schemas.Feature);
Features.helpers({
usesLeft: function(){
return evaluate(this.charId, this.uses) - this.used;
},
usesValue: function(){
return evaluate(this.charId, this.uses);
},
});
Features.attachBehaviour("softRemovable");
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,19 +0,0 @@
Notes = new Mongo.Collection("notes");
Schemas.Note = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
color: {
type: String,
allowedValues:_.pluck(colorOptions, "key"),
defaultValue: "q",
},
});
Notes.attachSchema(Schemas.Note);
Notes.attachBehaviour("softRemovable");
Notes.allow(CHARACTER_SUBSCHEMA_ALLOW);
Notes.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,37 +0,0 @@
Proficiencies = new Mongo.Collection("proficiencies");
Schemas.Proficiency = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
name: {
type: String,
trim: false,
optional: true,
},
value: {
type: Number,
allowedValues: [0, 0.5, 1, 2],
defaultValue: 1,
decimal: true,
},
type: {
type: String,
allowedValues: ["skill", "save", "weapon", "armor", "tool", "language"],
defaultValue: "skill",
},
enabled: {
type: Boolean,
defaultValue: true,
},
});
Proficiencies.attachSchema(Schemas.Proficiency);
Proficiencies.attachBehaviour("softRemovable");
makeChild(Proficiencies, ["enabled"]);
Proficiencies.allow(CHARACTER_SUBSCHEMA_ALLOW);
Proficiencies.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,37 +0,0 @@
SpellLists = new Mongo.Collection("spellLists");
Schemas.SpellLists = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true, trim: false},
description: {type: String, optional: true, trim: false},
saveDC: {type: String, optional: true, trim: false},
attackBonus: {type: String, optional: true, trim: false},
maxPrepared: {type: String, optional: true, trim: false},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
"settings.showUnprepared": {type: Boolean, defaultValue: true},
});
SpellLists.attachSchema(Schemas.SpellLists);
SpellLists.helpers({
numPrepared: function(){
var num = 0;
Spells.find(
{charId: this.charId, listId: this._id, prepared: 1},
{fields: {prepareCost: 1}}
).forEach(function(spell){
num += spell.prepareCost;
});
return num;
}
});
SpellLists.attachBehaviour("softRemovable");
makeParent(SpellLists); //parents of spells
SpellLists.allow(CHARACTER_SUBSCHEMA_ALLOW);
SpellLists.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -1,252 +0,0 @@
Spells = new Mongo.Collection("spells");
Schemas.Spell = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
prepared: {
type: String,
defaultValue: "prepared",
allowedValues: ["prepared", "unprepared", "always"],
},
name: {
type: String,
optional: true,
trim: false,
defaultValue: "New Spell",
},
description: {
type: String,
optional: true,
trim: false,
},
castingTime: {
type: String,
optional: true,
defaultValue: "action",
trim: false,
},
range: {
type: String,
optional: true,
trim: false,
},
duration: {
type: String,
optional: true,
trim: false,
defaultValue: "Instantaneous",
},
"components.verbal": {type: Boolean, defaultValue: false},
"components.somatic": {type: Boolean, defaultValue: false},
"components.concentration": {type: Boolean, defaultValue: false},
"components.material": {type: String, optional: true},
ritual: {
type: Boolean,
defaultValue: false,
},
level: {
type: Number,
defaultValue: 1,
},
school: {
type: String,
defaultValue: "Abjuration",
allowedValues: magicSchools,
},
color: {
type: String,
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
});
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);
var checkMovePermission = function(spellId, parent, destinationOnly) {
var spell = Spells.findOne(spellId);
if (!spell)
throw new Meteor.Error("No such spell",
"An spell could not be found to move");
//handle permissions
var permission;
if (!destinationOnly) { //if we're not modifying the origin, only the destination
permission = Meteor.call("canWriteCharacter", spell.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move spells 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 spells 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 spells to this character");
}
}
}
};
var moveSpell = function(spellId, targetCollection, targetId) {
var spell = Spells.findOne(spellId);
if (!spell) return;
targetCollection = targetCollection || spell.parent.collection;
targetId = targetId || spell.parent.id;
if (Meteor.isServer) {
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, false);
}
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
targetList = SpellLists.findOne({"charId": targetId});
targetListId = targetList && targetList._id;
if (!targetListId) {
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
name: "New SpellList",
charId: targetId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
Spells.update(
spellId,
{$set: {
charId: targetId,
"parent.collection": "SpellLists",
"parent.id": targetListId,
}}
);
}
else { //we are moving the spell within the same character
//update the spell provided the update will actually change something
if (
spell.parent.collection !== targetCollection ||
spell.parent.id !== targetId
){
Spells.update(
spellId,
{$set: {
"parent.collection": targetCollection,
"parent.id": targetId,
}}
);
}
}
};
var copySpell = function(spellId, targetCollection, targetId) {
var spell = Spells.findOne(spellId);
if (!spell) return;
targetCollection = targetCollection || spell.parent.collection;
targetId = targetId || spell.parent.id;
if (Meteor.isServer) {
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, true); //we're only reading from the source character
}
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
targetList = SpellLists.findOne({"charId": targetId});
targetListId = targetList && targetList._id;
if (!targetListId) {
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
name: "New SpellList",
charId: targetId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
newSpell = _.clone(spell);
delete newSpell._id;
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
Spells.update(
newSpellId,
{$set: {
charId: targetId,
"parent.collection": "SpellLists",
"parent.id": targetListId,
}}
);
}
else { //else we are copying the spell within the same character
newSpell = _.clone(spell);
delete newSpell._id;
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
Spells.update(
newSpellId,
{$set: {
"parent.collection": targetCollection,
"parent.id": targetId,
}}
);
}
};
Meteor.methods({
moveSpellToList: function(spellId, spellListId) {
check(spellId, String);
check(spellListId, String);
moveSpell(spellId, "SpellLists", spellListId);
},
copySpellToList: function(spellId, spellListId) {
check(spellId, String);
check(spellListId, String);
copySpell(spellId, "SpellLists", spellListId);
},
moveSpellToCharacter: function(spellId, charId) {
check(spellId, String);
check(charId, String);
moveSpell(spellId, "Characters", charId);
},
copySpellToCharacter: function(spellId, charId) {
check(spellId, String);
check(charId, String);
copySpell(spellId, "Characters", charId);
},
});

View File

@@ -1,21 +0,0 @@
/*
* Adjustments make instantaneous changes to the value of some attribute
* Damage, healing and resource cost/recovery are all adjustments
*/
Schemas.Adjustment = new SimpleSchema({
//which stat the adjustment is applied to
stat: {
type: String,
optional: true,
},
//the value added to the stat
value: {
type: Number,
decimal: true,
optional: true,
},
calculation: {
type: String,
optional: true,
},
});

View File

@@ -1,13 +0,0 @@
Schemas.Attribute = new SimpleSchema({
//the temporary shift of the attribute
//should be zero after reset
adjustment: {
type: Number,
defaultValue: 0,
},
reset: {
type: String,
defaultValue: "longRest",
allowedValues: ["longRest", "shortRest"],
},
});

View File

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

View File

@@ -1,41 +0,0 @@
TemporaryHitPoints = new Mongo.Collection("temporaryHitPoints");
Schemas.TemporaryHitPoints = new SimpleSchema({
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
name: {type: String, optional: true},
maximum: {type: Number, defaultValue: 0, min: 0, max: 500},
used: {type: Number, defaultValue: 0, min: 0, max: 500},
deleteOnZero:{type: Boolean, defaultValue: false},
dateAdded: {
type: Date,
autoValue: function() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return {$setOnInsert: new Date()};
} else {
this.unset();
}
},
},
});
TemporaryHitPoints.attachSchema(Schemas.TemporaryHitPoints);
TemporaryHitPoints.helpers({
left: function(){
return this.maximum - this.used;
}
});
//remove the temporary hit points when they hit zero
TemporaryHitPoints.after.update(
function(userId, thp, fieldNames, modifier, options){
if (thp.used >= thp.maximum && thp.deleteOnZero){
TemporaryHitPoints.remove(thp._id);
}
}, {fetchPrevious: false}
);
TemporaryHitPoints.allow(CHARACTER_SUBSCHEMA_ALLOW);
TemporaryHitPoints.deny(CHARACTER_SUBSCHEMA_DENY);

View File

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

View File

@@ -1,268 +0,0 @@
Items = new Mongo.Collection("items");
Schemas.Item = new SimpleSchema({
name: {type: String, optional: true, trim: false, defaultValue: "New Item"},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},
charId: {type: String, regEx: SimpleSchema.RegEx.Id, 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"),
defaultValue: "q",
},
});
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;
},
totalWeight: function(){
return this.weight * this.quantity;
},
pluralName: function(){
if (this.plural && this.quantity !== 1){
return this.plural;
} else {
return this.name;
}
},
});
Items.before.update(function(userId, doc, fieldNames, modifier, options){
if (
modifier && modifier.$set && modifier.$set.enabled && //we are equipping this item
!(
modifier.$set["parent.collection"] === "Characters" &&
modifier.$set["parent.id"]
) //and we haven"t specified a character to equip to
){
//equip it to the current character
modifier.$set["parent.collection"] = "Characters";
modifier.$set["parent.id"] = doc.charId;
}
});
Items.attachBehaviour("softRemovable");
makeChild(Items); //children of containers
makeParent(Items, ["name", "enabled"]); //parents of effects and attacks
Items.allow(CHARACTER_SUBSCHEMA_ALLOW);
//give characters default items
Characters.after.insert(function(userId, char) {
if (Meteor.isServer){
var containerId = Containers.insert({
name: "Coin Pouch",
charId: char._id,
isCarried: true,
description: "A sturdy pouch for coins",
color: "d",
});
Items.insert({
name: "Gold piece",
plural: "Gold pieces",
charId: char._id,
quantity: 0,
weight: 0.02,
value: 1,
color: "n",
parent: {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Silver piece",
plural: "Silver pieces",
charId: char._id,
quantity: 0,
weight: 0.02,
value: 0.1,
color: "q",
parent: {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
Items.insert({
name: "Copper piece",
plural: "Copper pieces",
charId: char._id,
quantity: 0,
weight: 0.02,
value: 0.01,
color: "s",
parent: {
id: containerId,
collection: "Containers",
},
settings: {
showIncrement: true,
},
});
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,105 +0,0 @@
Schemas.UserProfile = new SimpleSchema({
username: {
type: String,
optional: true,
},
});
Schemas.User = new SimpleSchema({
username: {
type: String,
optional: true,
},
profile: {
type: Schemas.UserProfile,
optional: true,
},
emails: {
type: Array,
optional: true,
},
"emails.$": {
type: Object,
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email,
},
"emails.$.verified": {
type: Boolean,
},
registered_emails: {
type: Array,
optional: true,
},
"registered_emails.$": {
type: Object,
blackbox: true,
},
createdAt: {
type: Date
},
services: {
type: Object,
optional: true,
blackbox: true,
},
roles: {
type: Object,
optional: true,
blackbox: true,
},
roles: {
type: Array,
optional: true,
},
"roles.$": {
type: String
},
// In order to avoid an 'Exception in setInterval callback' from Meteor
heartbeat: {
type: Date,
optional: true,
},
apiKey: {
type: String,
index: 1,
optional: true,
},
});
Meteor.users.attachSchema(Schemas.User);
Meteor.users.allow({
update: function(userId, doc, fields, modifier) {
if (
doc._id === userId &&
_.contains(fields, "username") &&
_.contains(fields, "profile") &&
fields.length === 2 &&
_.keys(modifier).length === 1 &&
modifier.$set &&
modifier.$set["profile.username"] &&
modifier.$set.username &&
_.keys(modifier.$set).length === 2
){
var expectedUsername = modifier.$set["profile.username"];
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
if (modifier.$set.username !== expectedUsername){
return false;
}
var foundUser = Meteor.call("getUserId", expectedUsername);
return !foundUser || foundUser === userId;
}
}
});
if (Meteor.isServer) Meteor.methods({
generateMyApiKey() {
var user = Meteor.users.findOne(this.userId);
if (!user) return;
if (user && user.apiKey) return;
var apiKey = Random.id(30);
Meteor.users.update(this.userId, {$set: {apiKey}});
},
});

View File

@@ -1,91 +0,0 @@
Router.map(function() {
this.route("vmixCharacter", {
path: "/vmix-character/:_id/",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "vmixCharacter", () =>
this.response.end(vMixCharacter(this.params._id))
);
},
});
this.route("vmixParty", {
path: "/vmix-party/:_id/",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "vmixParty", () =>
this.response.end(vMixParty(this.params._id))
);
},
});
this.route("jsonCharacterSheet", {
path: "/character/:_id/json",
where: "server",
action: function() {
this.response.setHeader("Content-Type", "application/json");
var query = this.params.query;
var key = query && query.key;
ifKeyValid(key, this.response, "jsonCharacterSheet", () => {
if (canViewCharacter(this.params._id, userIdFromKey(key))){
this.response.end(JSONExport(this.params._id))
} else {
this.response.writeHead(403, "You do not have permission to view this character");
this.response.end();
}
}
);
},
});
});
var ifKeyValid = function(apiKey, response, method, callback){
if (!apiKey){
response.writeHead(403, "You must use an api key to access this api");
response.end();
} else if (!isKeyValid(apiKey)){
response.writeHead(403, "API key is invalid");
response.end();
} else if (isRateLimited(apiKey, method)){
response.writeHead(429, "Too many requests");
response.end(JSON.stringify({
"timeToReset": rateLimiter.check({apiKey: apiKey, method: method}).timeToReset
}));
} else {
rateLimiter.increment({apiKey: apiKey, method: method})
callback();
}
};
var isKeyValid = function(apiKey){
var user = Meteor.users.findOne({apiKey});
if (!user) return false;
var blackListed = Blacklist.findOne({userId: user._id});
return !blackListed;
};
var userIdFromKey = function(apiKey){
var user = Meteor.users.findOne({apiKey}); // we know user exists from isKeyValid
return user._id;
}
var rateLimiter = new RateLimiter();
rateLimiter.addRule({apiKey: String}, 5, 5000);
rateLimiter.addRule({apiKey: String, method: "vmixCharacter"}, 2, 10000);
rateLimiter.addRule({apiKey: String, method: "vmixParty"}, 2, 10000);
rateLimiter.addRule({apiKey: String, method: "jsonCharacterSheet"}, 5, 5000);
var isRateLimited = function(apiKey, method){
const limited = !rateLimiter.check({apiKey: apiKey, method: method}).allowed
if (limited) {
console.log(`Rate limit hit by API key ${apiKey}`);
return true;
} else {
return false;
}
};

View File

@@ -1,174 +0,0 @@
Router.configure({
loadingTemplate: "loading",
layoutTemplate: "layout",
trackPageView: true,
});
Router.plugin("ensureSignedIn", {
only: [
"profile",
"characterList",
]
});
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
var handleSubError = function(e){
Session.set("error", {reason: e.reason, href: location.href});
Router.go("/error");
};
Router.map(function() {
this.route("/", {
name: "home",
onAfterAction: function() {
document.title = appName;
},
});
this.route("characterList", {
path: "/characterList",
waitOn: function(){
return subsManager.subscribe("characterList");
},
onAfterAction: function() {
document.title = appName + " - Characters";
},
fastRender: true,
});
this.route("characterSheetNaked", {
path: "/character/:_id/",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
action: function(){
var _id = this.params._id
var character = Characters.findOne(_id);
var urlName = character && character.urlName;
var path = `\/character\/${_id}\/${urlName || "-"}`;
Router.go(path,{},{replaceState:true});
},
});
this.route("characterSheet", {
path: "/character/:_id/:urlName",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name;
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/character");
this.next();
},
fastRender: true,
});
this.route("printedCharacterSheet", {
path: "/character/:_id/:urlName/print",
waitOn: function(){
return [
subsManager.subscribe(
"singleCharacter", this.params._id, {onError: handleSubError}
),
];
},
data: function() {
var data = Characters.findOne(
{_id: this.params._id},
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
);
return data;
},
onAfterAction: function() {
var char = Characters.findOne({_id: this.params._id}, {fields: {name: 1}});
var name = char && char.name;
if (name){
document.title = name + " - Printing";
}
},
//analytics
trackPageView: false,
onRun: function() {
window.ga && window.ga("send", "pageview", "/print-character");
this.next();
},
});
this.route("library", {
path: "/library",
waitOn: function(){
return subsManager.subscribe("standardLibraries");
},
onAfterAction: function() {
document.title = appName + " - Library";
},
fastRender: true,
});
this.route("loading", {
path: "/loading"
});
this.route("profile", {
path: "/account",
onAfterAction: function() {
document.title = appName + " Account";
},
});
this.route("/changelog", {
name: "changeLog",
waitOn: function() {
return [
subsManager.subscribe("changeLog"),
]
},
data: {
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;
},
});
this.route("/error", {
name: "error",
onAfterAction: function() {
document.title = `${appName} - Error`;
},
});
});

View File

@@ -1,30 +0,0 @@
{
"name": "RPG Docs",
"version": "0.0.0",
"homepage": "",
"authors": [
"Stefan Zermatten"
],
"license": "none",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"polymer": "Polymer/polymer#^1.0.0",
"iron-elements": "PolymerElements/iron-elements#^1.0.0",
"platinum-elements": "PolymerElements/platinum-elements#^1.0.1",
"neon-elements": "PolymerElements/neon-elements#^1.0.0",
"paper-elements": "PolymerElements/paper-elements#^1.0.7",
"app-elements": "PolymerElements/app-elements#^0.10.1",
"marked-element": "PolymerElements/marked-element#^1.2.0",
"paper-swatch-picker": "PolymerElements/paper-swatch-picker#~1.0.2"
},
"resolutions": {
"webcomponentsjs": "0.7.24"
}
}

View File

@@ -1,174 +0,0 @@
// jscs:disable
// https://github.com/chunksnbits/jquery-quickfit
(function ($) {
var Quickfit, QuickfitHelper, defaults, pluginName;
pluginName = 'quickfit';
defaults = {
min: 8,
max: 12,
tolerance: 0.02,
truncate: false,
width: null,
sampleNumberOfLetters: 10,
sampleFontSize: 12
};
QuickfitHelper = (function () {
var sharedInstance = null;
QuickfitHelper.instance = function (options) {
if (!sharedInstance) {
sharedInstance = new QuickfitHelper(options);
}
return sharedInstance;
};
function QuickfitHelper(options) {
this.options = options;
this.item = $('<span id="meassure"></span>');
this.item.css({
position: 'absolute',
left: '-1000px',
top: '-1000px',
'font-size': "" + this.options.sampleFontSize + "px"
});
$('body').append(this.item);
this.meassures = {};
}
QuickfitHelper.prototype.getMeassure = function (letter) {
var currentMeassure;
currentMeassure = this.meassures[letter];
if (!currentMeassure) {
currentMeassure = this.setMeassure(letter);
}
return currentMeassure;
};
QuickfitHelper.prototype.setMeassure = function (letter) {
var currentMeassure, index, sampleLetter, text, _ref;
text = '';
sampleLetter = letter === ' ' ? '&nbsp;' : letter;
for (index = 0, _ref = this.options.sampleNumberOfLetters - 1; 0 <= _ref ? index <= _ref : index >= _ref; 0 <= _ref ? index++ : index--) {
text += sampleLetter;
}
this.item.html(text);
currentMeassure = this.item.width() / this.options.sampleNumberOfLetters / this.options.sampleFontSize;
this.meassures[letter] = currentMeassure;
return currentMeassure;
};
return QuickfitHelper;
})();
Quickfit = (function () {
function Quickfit(element, options) {
this.$element = element;
this.options = $.extend({}, defaults, options);
this.$element = $(this.$element);
this._defaults = defaults;
this._name = pluginName;
this.quickfitHelper = QuickfitHelper.instance(this.options);
}
Quickfit.prototype.fit = function () {
var elementWidth;
if (!this.options.width) {
elementWidth = this.$element.width();
this.options.width = elementWidth - this.options.tolerance * elementWidth;
}
if (this.text = this.$element.attr('data-quickfit')) {
this.previouslyTruncated = true;
} else {
this.text = this.$element.text();
}
this.calculateFontSize();
if (this.options.truncate) this.truncate();
return {
$element: this.$element,
size: this.fontSize
};
};
Quickfit.prototype.calculateFontSize = function () {
var letter, textWidth, i;
textWidth = 0;
for (i = 0; i < this.text.length; ++i) {
letter = this.text.charAt(i);
textWidth += this.quickfitHelper.getMeassure(letter);
}
this.targetFontSize = parseInt(this.options.width / textWidth);
return this.fontSize = Math.max(this.options.min, Math.min(this.options.max, this.targetFontSize));
};
Quickfit.prototype.truncate = function () {
var index, lastLetter, letter, textToAdd, textWidth;
if (this.fontSize > this.targetFontSize) {
textToAdd = '';
textWidth = 3 * this.quickfitHelper.getMeassure('.') * this.fontSize;
index = 0;
while (textWidth < this.options.width && index < this.text.length) {
letter = this.text[index++];
if (lastLetter) textToAdd += lastLetter;
textWidth += this.fontSize * this.quickfitHelper.getMeassure(letter);
lastLetter = letter;
}
if (textToAdd.length + 1 === this.text.length) {
textToAdd = this.text;
} else {
textToAdd += '...';
}
this.textWasTruncated = true;
return this.$element.attr('data-quickfit', this.text).html(textToAdd);
} else {
if (this.previouslyTruncated) {
return this.$element.html(this.text);
}
}
};
return Quickfit;
})();
return $.fn.quickfit = function (options) {
var measurements = [];
// Separate measurements from repaints
// First calculate all measurements...
var $elements = this.each(function () {
var measurement = new Quickfit(this, options).fit();
measurements.push(measurement);
return measurement.$element;
});
// ... then apply the measurements.
for (var i = 0; i < measurements.length; i++) {
var measurement = measurements[i];
measurement.$element.css({ fontSize: measurement.size + 'px' });
}
return $elements;
};
})(jQuery, window);

View File

@@ -1,38 +0,0 @@
this.GlobalUI = (function() {
function GlobalUI() {}
var toast;
GlobalUI.toast = function(opts) {
if (!toast) toast = $("#global-toast")[0];
if (_.isObject(opts)){
toast.text = opts.text;
Session.set("global.ui.toastTemplate", opts.template);
Session.set("global.ui.toastData", opts.data);
} else {
toast.text = opts;
Session.set("global.ui.toastTemplate");
Session.set("global.ui.toastData");
}
return toast.show();
};
GlobalUI.deletedToast = function(id, collection, itemName) {
GlobalUI.toast({
text: itemName ? itemName + " deleted" : "Deleted item from" + collection,
template: "undoToast",
data: {
id: id,
collection: collection,
},
});
};
return GlobalUI;
})();
Template.layout.helpers({
globalToastTemplate: function() {
return Session.get("global.ui.toastTemplate");
},
globalToastData: function() {
return Session.get("global.ui.toastData");
},
});

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
Template.registerHelper("characterPath", function(char) {
return `\/character\/${char._id}\/${char.urlName || "-"}`;
});

View File

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

View File

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

View File

@@ -1,37 +0,0 @@
Template.registerHelper("evaluate", function(charId, string) {
return evaluate(charId, string);
});
Template.registerHelper("evaluateSigned", function(charId, string) {
var number = evaluate(charId, string);
if (_.isFinite(number)) {
return number > 0 ? "+" + number : "" + number;
} else {
return number;
}
});
Template.registerHelper("evaluateSignedSpaced", function(charId, string) {
var number = evaluate(charId, string);
if (_.isFinite(number)) {
return number > 0 ? "+ " + number : "- " + (-1 * number);
} else {
return number;
}
});
Template.registerHelper("evaluateString", function(charId, string) {
return evaluateString(charId, string);
});
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
return evaluateSpellString(charId, spellListId, string);
});
Template.registerHelper("evaluateShortString", function(charId, string) {
if (_.isString(string)){
return evaluateString(
charId, string.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
);
}
});

View File

@@ -1,26 +0,0 @@
openParentDialog = function({
parent, charId, element, returnElement, callback,
}) {
let template;
let data;
if (parent.collection === "Characters" && parent.group === "racial") {
template = "raceDialog";
data = {charId: parent.id};
} else if (parent.collection === "Features") {
template = "featureDialog";
data = {featureId: parent.id};
} else if (parent.collection === "Classes") {
template = "classDialog";
data = {classId: parent.id};
} else if (parent.collection === "Items") {
template = "itemDialog";
data = {itemId: parent.id};
} else if (parent.collection === "Spells") {
template = "spellDialog";
data = {spellId: parent.id};
} else if (parent.collection === "Buffs") {
template = "buffDialog";
data = {buffId: parent.id};
}
pushDialogStack({template, data, element, returnElement, callback});
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,8 @@
<head>
<meta name="viewport" content="width=device-width initial-scale=1.0, user-scalable=no">
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<meta name="viewport" content="width=device-width initial-scale=1.0, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,500,500italic,700,700italic,900,900italic,100italic,100&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png?v=lk6WXp6Pmj">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png?v=lk6WXp6Pmj">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png?v=lk6WXp6Pmj">

3
app/client/index.html Normal file
View File

@@ -0,0 +1,3 @@
<body>
<div id="app"></div>
</body>

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
Session.setDefault("isPrinting", false);
if (window.matchMedia) {
var mediaQueryList = window.matchMedia("print");
mediaQueryList.addListener(function(mql) {
if (mql.matches) {
Session.set("isPrinting", true);
Tracker.flush();
} else {
Session.set("isPrinting", false);
}
});
}

View File

@@ -1,17 +0,0 @@
removeDuplicateProficiencies = function(proficiencies) {
dict = {};
proficiencies.forEach(function(prof) {
if (prof.name in dict) { //if we have already gone over another proficiency for the same thing
if (dict[prof.name].value < prof.value) {
dict[prof.name] = prof; //then take the new one if it's higher, otherwise leave it
}
} else {
dict[prof.name] = prof; //if it wasn't already there, store it
}
});
profs = []
_.forEach(dict, function(prof) {
profs.push(prof);
})
return profs;
};

View File

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

3
app/client/main.js Normal file
View File

@@ -0,0 +1,3 @@
import '/imports/ui/vueSetup.js';
import '/imports/ui/styles/stylesIndex.js';
import '/imports/ui/styles/stylesIndex.js';

View File

@@ -1,17 +0,0 @@
@keyframes bounce {
from {
transform: translate(0px,0px);
}
to {
transform: translate(0px,-16px);
}
}
.bounce{
animation-name: bounce;
animation-duration: 0.3s;
animation-direction: alternate;
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
animation-delay: 0s;
animation-iteration-count: infinite;
}

View File

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

View File

@@ -1,87 +0,0 @@
.red {
background-color: #F44336;
}
.pink {
background-color: #E91E63;
}
.purple {
background-color: #9C27B0;
}
.deep-purple {
background-color: #673AB7;
}
.indigo {
background-color: #3F51B5;
}
.blue {
background-color: #2196F3;
}
.light-blue {
background-color: #03A9F4;
}
.cyan {
background-color: #00BCD4;
}
.teal {
background-color: #009688;
}
.green {
background-color: #4CAF50;
}
.light-green {
background-color: #8BC34A;
}
.lime {
background-color: #CDDC39;
}
.yellow {
background-color: #FFEB3B;
}
.amber {
background-color: #FFC107;
}
.orange {
background-color: #FF9800;
}
.deep-orange {
background-color: #FF5722;
}
.brown {
background-color: #795548;
}
.grey {
background-color: #9E9E9E;
}
.blue-grey {
background-color: #607D8B;
}
.app-grey {
background-color: #424242;
}
.white {
background-color: #ffffff;
}
.black {
background-color: #262626;
}

View File

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

View File

@@ -1,115 +0,0 @@
/*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;
}
/*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;
}
.avatar {
display: inline-block;
box-sizing: border-box;
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 26px;
font-color: rgba(255, 255, 255, 0.58) !important;
}
/*
temporary fix for https://github.com/PolymerElements/paper-item/issues/71
*/
paper-icon-item::shadow #contentIcon {
flex-shrink: 0;
}
/*FABs*/
.floatyButton {
position: fixed;
bottom: 24px;
right: 24px;
/* stop the fab from flashing during animation */
transform: scale(1) translateZ(0px);
z-index: 3;
}
paper-fab {
background-color: #d13b2e;
}
paper-fab.keyboard-focus {
background: #630c05;
}
.red-button:not([disabled]) {
background: #d23f31;
color: #fff;
margin-top: 16px;
}
/*Buttons*/
paper-button {
color: #000;
color: rgba(0,0,0,0.87);
font-size: 14px;
font-weight: 400;
letter-spacing: 0.010;
text-transform: uppercase;
}
dicecloud-selector paper-item {
white-space: nowrap;
overflow: hidden;
}
/*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

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

View File

@@ -1,68 +0,0 @@
body .paper-font-display4, body .paper-font-display3, body .paper-font-title, body .paper-font-caption{
white-space: normal;
}
.white-text {
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text .paper-font-display2{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.white-text .paper-font-display1, .white-text.paper-font-display1{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.white-text h1, .white-text .paper-font-headline, .white-text.paper-font-headline{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text h2, .white-text .paper-font-title, .white-text.paper-font-title{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text h3, .white-text .paper-font-subhead{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text .paper-font-body2{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text p, .white-text .paper-font-body1{
color: #dedede;
color: rgba(255,255,255,0.87);
}
.white-text .paper-font-caption{
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.black54 {
color: #757575;
color: rgba(0,0,0,0.54);
}
.white54 {
color: #8a8a8a;
color: rgba(255,255,255,0.54);
}
.black87 {
color: #212121;
color: rgba(0,0,0,0.87);
}
.white87 {
color: #dedede;
color: rgba(255,255,255,0.87);
}

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
<!--needs to be given charId, parentId and parentCollection-->
<template name="attackEditList">
{{#if attacks.count}}
<hr style="margin: 16px 0 16px 0;">
<div id="attacks">
<div class="paper-font-title">Attacks</div>
{{#each attacks}}
{{>attackEdit}}
{{/each}}
</div>
{{/if}}
<paper-button id="addAttackButton" class="red-button" raised>Add Attack</paper-button>
</template>

View File

@@ -1,38 +0,0 @@
Template.attackEditList.helpers({
attacks: function() {
var cursor = Attacks.find({"parent.id": this.parentId, charId: this.charId});
return cursor;
}
});
Template.attackEditList.events({
"tap #addAttackButton": function() {
if (typeof this.isSpell !== 'undefined' && this.isSpell) {
var parentSpell = Spells.findOne({"_id": this.parentId})
if (parentSpell && parentSpell.parent.collection == "SpellLists") {
var spellList = SpellLists.findOne({"_id":parentSpell.parent.id});
if (spellList && spellList.attackBonus) {
Attacks.insert({
charId: this.charId,
parent: {
id: this.parentId,
collection: this.parentCollection
},
attackBonus: "attackBonus",
damage: "1d10",
damageType: "fire",
});
return;
}
}
}
Attacks.insert({
charId: this.charId,
parent: {
id: this.parentId,
collection: this.parentCollection
},
});
},
});

View File

@@ -1,17 +0,0 @@
<template name="attackView">
<div class="attackView layout horizontal">
<div class="paper-font-headline layout horizontal center" style="margin-right: 16px;">
{{evaluateAttackBonus charId attack}}
</div>
<div class="layout vertical">
<div>
{{evaluateDamage charId attack}}&nbsp;{{attack.damageType}}
</div>
{{#if attack.details}}
<div class="paper-font-caption">
{{attack.details}}
</div>
{{/if}}
</div>
</div>
</template>

View File

@@ -1,28 +0,0 @@
Template.attackView.helpers ({
evaluateAttackBonus: function(charId, attack) {
if (attack.parent.collection == "Spells") {
var spell = Spells.findOne(attack.parent.id);
if (spell) {
bonus = evaluate(charId, attack.attackBonus, {"spellListId": spell.parent.id});
}
} else {
var bonus = evaluate(charId, attack.attackBonus);
}
if (_.isFinite(bonus)) {
return bonus > 0 ? "+" + bonus : "" + bonus;
} else {
return bonus;
}
},
evaluateDamage: function(charId, attack) {
if (attack.parent.collection == "Spells") {
var spell = Spells.findOne(attack.parent.id);
if (spell) {
return evaluateSpellString(charId, spell.parent.id, attack.damage);
}
} else {
return evaluateString(charId, attack.damage);
}
},
})

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
<!-- data is the CustomBuff -->
<template name="applyBuffDialog">
<div class="fit layout vertical applyBuffDialog">
<app-header fixed effects="waterfall">
<app-toolbar>
Apply Buff
</app-toolbar>
</app-header>
<div class="flex layout horizontal" style="height:100%">
<div class="flex" style="margin-right: 16px; height: 100%; max-width: 240px; overflow-y: auto;">
{{> characterPicker selfId=buff.charId includeSelf=canApplyToSelf writableOnly=true}}
</div>
<div class="flex buff-description" style="height: 100%; overflow-y: auto">
<hr style="margin: 16px 0 16px 0;">
{{#if buff.description}}
<div>{{#markdown}}{{evaluateString buff.charId buff.description}}{{/markdown}}</div>
<hr style="margin: 16px 0 16px 0;">
{{/if}}
{{> effectsViewList charId=buff.charId parentId=buff._id}}
{{> proficiencyViewList charId=buff.charId parentId=buff._id}}
{{> attacksViewList charId=buff.charId parentId=buff._id}}
</div>
</div>
<div class="buttons layout horizontal end-justified">
<paper-button id="cancelButton">
Cancel
</paper-button>
<paper-button id="applyButton" disabled={{cantApply}}>
Apply
</paper-button>
</div>
</div>
</template>

View File

@@ -1,32 +0,0 @@
Template.applyBuffDialog.onCreated(function(){
this.selectedTarget = new ReactiveVar("default");
});
Template.applyBuffDialog.helpers({
cantApply: function() {
return this.buff.target === "others" && Template.instance().selectedTarget.get() === "default"; //this is the only case where we can't apply a buff
},
canApplyToSelf: function() {
return this.buff.target !== "others"; //i.e. it is "self" or "both"
},
});
Template.applyBuffDialog.events({
"iron-select .characterPicker": function(event){
var detail = event.originalEvent.detail;
var value = detail.item.getAttribute("name");
Template.instance().selectedTarget.set(value);
},
"click #applyButton": function(event, instance){
var targetId = Template.instance().selectedTarget.get();
if (targetId === "default") {
if (this.buff.target === "others") return; //since we have "Select a character" selected
targetId = this.buff.charId; //otherwise, the default is to target self
}
popDialogStack(targetId);
},
"click #cancelButton": function(event, instance){
popDialogStack();
},
});

View File

@@ -1,23 +0,0 @@
<template name="buffDialog">
{{#with buff}}
{{#baseDialog title=name class="white" hideColor=true startEditing=true editOnly=true}}
{{> buffDetails}}
{{else}}
{{> buffDetails}}
{{/baseDialog}}
{{/with}}
</template>
<template name="buffDetails">
<div>
{{appliedBy}}
</div>
<hr style="margin: 16px 0 16px 0;">
{{#if description}}
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
<hr style="margin: 16px 0 16px 0;">
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> proficiencyViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}
</template>

View File

@@ -1,50 +0,0 @@
Template.buffDialog.onCreated(function(){
var buff = Buffs.findOne(this.buffId);
Meteor.subscribe("singleCharacterName", buff.charId); //so we can access the names of public characters
});
Template.buffDialog.helpers({
buff: function(){
return Buffs.findOne(this.buffId);
},
});
Template.buffDialog.events({
"click #deleteButton": function(event, instance){
Buffs.softRemoveNode(instance.data.buffId);
popDialogStack();
},
});
const typeDict = {
"Features": "feature",
"Items": "item",
"Spells": "spell",
}; //really, we should only need these three
Template.buffDetails.helpers({
appliedBy: function() {
if (this.type == "inate") {
return "Innate.";
} else {
var myName = Characters.findOne(this.charId).name;
var applierCharacter = Characters.findOne(this.appliedBy) || {name: "???"}
// "???" indicates that either we do not have read access to the buff-giver, or that the buff-giver does not exist.
if (applierCharacter.name === myName) {
var charName = "your "
} else {
if (applierCharacter.name && applierCharacter.name[applierCharacter.name.length - 1] === 's') {
var charName = applierCharacter.name + "' ";
} else {
var charName = applierCharacter.name + "'s ";
}
}
var type = typeDict[this.appliedByDetails.collection] + " ";
var applierThing = this.appliedByDetails.name;
return "Applied by " + charName + type + applierThing + ".";
}
},
});

View File

@@ -1,15 +0,0 @@
<template name="buffListItem">
<div class="item buffListItem layout horizontal center">
<div class="flex">
{{buff.name}}
</div>
{{#if canEditCharacter buff.charId}}
<paper-icon-button class="deleteButton"
role="button"
tabindex="0"
icon="delete">
</paper-icon-button>
{{/if}}
</div>
</template>

View File

@@ -1,21 +0,0 @@
Template.buffListItem.helpers({
name: function() {
return this.buff.name
}
});
Template.buffListItem.events({
"click .buffListItem": function(event){
var buffId = this.buff._id;
var charId = this.buff.charId;
pushDialogStack({
template: "buffDialog",
data: {buffId: buffId, charId: charId},
element: event.currentTarget,
});
},
"tap .deleteButton": function(event){
event.stopPropagation();
Buffs.remove(this.buff._id);
},
});

View File

@@ -1,11 +0,0 @@
.condition-library-dialog .item.selected {
background-color: #e4e4e4;
}
.condition-library-dialog table {
border-collapse: collapse;
}
.condition-library-dialog .library-condition td, tr {
position: relative;
}

View File

@@ -1,34 +0,0 @@
<template name="conditionLibraryDialog">
<div class="fit condition-library-dialog layout vertical">
<app-toolbar class="app-grey white-text">
<paper-icon-button id="backButton"
icon="arrow-back">
</paper-icon-button>
<div main-title>Conditions</div>
</app-toolbar>
<div class="flex scroll-y">
<div class="conditions" style="padding:8px">
<table style="width: 100%">
<tbody>
{{#each condition in conditions}}
{{>libraryCondition condition=condition selected=(isSelected condition)}}
{{/each}}
</tbody>
</table>
</div>
</div>
<div class="layout horizontal end-justified">
<paper-button class="cancelButton">Cancel</paper-button>
<paper-button class="okButton">OK</paper-button>
</div>
</div>
</template>
<template name="libraryCondition">
<tr class="item library-condition {{#if selected}}selected{{/if}}">
<td class="conditionName">
{{conditionName condition}}
<paper-ripple></paper-ripple>
</td>
</tr>
</template>

View File

@@ -1,166 +0,0 @@
Template.conditionLibraryDialog.onCreated(function(){
this.selectedCondition = new ReactiveVar();
});
Template.conditionLibraryDialog.helpers({
conditions: function(){
return Object.keys(LIBRARY_CONDITIONS)
},
isSelected(condition){
const selected = Template.instance().selectedCondition.get();
return selected && selected === condition;
},
});
Template.conditionLibraryDialog.events({
"click .cancelButton": function(event, template){
popDialogStack();
},
"click .okButton": function(event, template){
popDialogStack(template.selectedCondition.get());
},
"click .library-condition": function(event, template){
template.selectedCondition.set(this.condition);
},
"click #backButton": function(event, template){
popDialogStack();
},
});
Template.libraryCondition.helpers({
conditionName: function(name){
return LIBRARY_CONDITIONS[name].buff.name;
},
})
LIBRARY_CONDITIONS = {
//Conditions
blind: {
buff: {
name: "Blind",
description: "A blinded creature cant see and automatically fails any ability check that requires sight.\n\nAttack rolls against the creature have advantage, and the creatures attack rolls have disadvantage.",
},
},
deaf: {
buff: {
name: "Deaf",
description: "A deafened creature cant hear and automatically fails any ability check that requires hearing.",
},
},
frightened: {
buff: {
name: "Frightened",
description: "A frightened creature has disadvantage on ability checks and attack rolls while the source of its fear is within line of sight.\n\nThe creature cant willingly move closer to the source of its fear.",
}
},
grappled: {
buff:{
name: "Grappled",
description: "A grappled creatures speed becomes 0, and it cant benefit from any bonus to its speed.\n\nThe condition ends if the grappler is incapacitated.\n\nThe condition also ends if an effect removes the grappled creature from the reach of the grappler or grappling effect, such as when a creature is hurled away by the thunder wave spell.",
},
},
incapacitated: {
buff: {
name: "Incapacitated",
description: "An incapacitated creature cant take actions or reactions.",
}
},
invisible: {
buff: {
name: "Invisible",
description: "An invisible creature is impossible to see without the aid of magic or a special sense. For the purpose of hiding, the creature is heavily obscured. The creatures location can be detected by any noise it makes or any tracks it leaves.\n\nAttack rolls against the creature have disadvantage, and the creatures attack rolls have advantage.",
}
},
paralyzed: {
buff: {
name: "Paralyzed",
description: "A paralyzed creature is **incapacitated** and cant move or speak.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
},
},
petrified: {
buff: {
name: "Petrified",
description: "A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.\n\nA petrified creature is **incapacitated** and cant move or speak, and is unaware of its surroundings.\n\nAttack rolls against the creature have advantage.\n\nThe creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
},
},
poisoned: {
buff: {
name: "Poisoned",
description: "A poisoned creature has disadvantage on attack rolls and ability checks.",
},
},
prone: {
buff: {
name: "Prone",
description: "A prone creatures only movement option is to crawl, unless it stands up and thereby ends the condition.\n\nThe creature has disadvantage on attack rolls.\n\nAn attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the attack roll has disadvantage.",
}
},
restrained: {
buff: {
name: "Restrained",
description: "A restrained creatures speed becomes 0, and it cant benefit from any bonus to its speed.\n\nAttack rolls against the creature have advantage, and the creatures attack rolls have disadvantage.\n\nThe creature has disadvantage on Dexterity saving throws.",
},
},
stunned: {
buff: {
name: "Stunned",
description: "A stunned creature is **incapacitated**, cant move, and can speak only falteringly\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.",
},
},
unconscious: {
buff: {
name: "Unconscious",
description: "An unconscious creature is **incapacitated**, cant move or speak, and is unaware of its surroundings.\n\nThe creature drops whatever its holding and falls **prone**.\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
},
},
exhaustion1: {
buff: {
name: "Exhaustion - 1",
description: "Disadvantage on ability checks\n\nFinishing a long rest reduces a creatures exhaustion level by 1, provided that the creature has also ingested some food and drink.",
},
},
exhaustion2: {
buff: {
name: "Exhaustion - 2",
description: "Speed halved",
},
},
exhaustion3: {
buff: {
name: "Exhaustion - 3",
description: "Disadvantage on attack rolls and saving throws",
},
},
exhaustion4: {
buff: {
name: "Exhaustion - 4",
description: "Hit point maximum halved",
},
},
exhaustion5: {
buff: {
name: "Exhaustion - 5",
description: "Speed reduced to 0",
},
},
exhaustion6: {
buff: {
name: "Exhaustion - 6",
description: "You have died of exhaustion",
},
},
};

View File

@@ -1,15 +0,0 @@
<template name="conditionView">
<div class="item conditionView layout horizontal center">
<div class="flex">
{{condition.name}}
</div>
{{#if canEditCharacter condition.charId}}
<paper-icon-button class="deleteButton"
role="button"
tabindex="0"
icon="delete">
</paper-icon-button>
{{/if}}
</div>
</template>

View File

@@ -1,15 +0,0 @@
Template.conditionView.events({
"click .conditionView": function(event){
var condition = this.condition;
var charId = Template.parentData()._id;
pushDialogStack({
template: "conditionViewDialog",
data: {condition: condition},
element: event.currentTarget,
});
},
"tap .deleteButton": function(event){
event.stopPropagation();
Conditions.remove(this.condition._id);
},
});

View File

@@ -1,14 +0,0 @@
<template name="conditionViewDialog">
{{#baseDialog title=condition.name class="white" hideColor=true startEditing=true editOnly=true}}}
{{> conditionDetails condition=condition}}
{{else}}
{{> conditionDetails condition=condition}}
{{/baseDialog}}
</template>
<template name="conditionDetails">
{{#if condition.description}}
<div>{{#markdown}}{{evaluateString condition.charId condition.description}}{{/markdown}}</div>
{{/if}}
{{> effectsViewList charId=condition.charId parentId=condition._id}}
</template>

View File

@@ -1,6 +0,0 @@
Template.conditionViewDialog.events({
"click #deleteButton": function(event, instance){
Conditions.remove(instance.data.condition._id);
popDialogStack();
},
});

View File

@@ -1,29 +0,0 @@
<template name="customBuffEdit">
{{#baseEditDialog title=buff.name hideColor=true}}
<!--name-->
<paper-input id="buffNameInput" class="fullwidth" label="Name" value={{buff.name}}></paper-input>
<div class="layout horizontal center wrap justified">
<paper-dropdown-menu class=flex label="Target" style="flex-basis: 150px; max-width: 200px;">
<dicecloud-selector selected={{buff.target}} class="dropdown-content target-dropdown">
<paper-item name="self" style="width: 150px;">
Self only
</paper-item>
<paper-item name="others">
Others only
</paper-item>
<paper-item name="both">
Both
</paper-item>
</dicecloud-selector>
</paper-dropdown-menu>
</div>
<!--description-->
<paper-textarea label="Description" id="buffDescriptionInput" value={{buff.description}}></paper-textarea>
{{> effectsEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
{{> attackEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
{{> proficiencyEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId enabled=false}}
{{/baseEditDialog}}
</template>

View File

@@ -1,47 +0,0 @@
Template.customBuffEdit.helpers({
buff(){
return CustomBuffs.findOne(this.customBuffId);
},
});
const debounce = (f) => _.debounce(f, 300);
Template.customBuffEdit.events({
"input #buffNameInput": debounce(function(event){
const input = event.currentTarget;
var name = input.value;
if (!name){
input.invalid = true;
input.errorMessage = "Name is required";
} else {
input.invalid = false;
CustomBuffs.update(this.customBuffId, {
$set: {name: name}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}
}),
"input #buffDescriptionInput": debounce(function(event){
var description = event.currentTarget.value;
CustomBuffs.update(this.customBuffId, {
$set: {description: description}
}, {
removeEmptyStrings: false,
trimStrings: false,
});
}),
"iron-select .target-dropdown": function(event){
var detail = event.originalEvent.detail;
var value = detail.item.getAttribute("name");
const buff = CustomBuffs.findOne(this.customBuffId);
if (value === buff.target) return;
CustomBuffs.update(this.customBuffId, {$set: {target: value}});
},
"click #deleteButton": function(event, instance){
CustomBuffs.softRemoveNode(instance.data.customBuffId);
GlobalUI.deletedToast(instance.data.customBuffId, "Buffs", "Buff");
popDialogStack();
},
});

View File

@@ -1,30 +0,0 @@
<!--needs to be given charId, parentId and parentCollection-->
<template name="customBuffEditList">
{{#if buffs.count}}
<div class="buffs">
<div class="paper-font-title" style="margin-bottom: 8px;">
Buffs
</div>
<table class="wideTable" style="width: 100%;">
{{#each buff in buffs}}
{{> customBuffEditListItem buff=buff}}
{{/each}}
</table>
</div>
{{/if}}
<paper-button id="addBuffButton"
class="red-button"
raised>
Add Buff
</paper-button>
</template>
<template name="customBuffEditListItem">
<div class="buff layout horizontal center" data-id={{buff._id}}>
{{> customBuffView buff=buff}}
<div>
<paper-icon-button class="edit-buff" icon="create">
</paper-icon-button>
</div>
</div>
</template>

View File

@@ -1,41 +0,0 @@
Template.customBuffEditList.helpers({
buffs: function(){
var selector = {
"parent.id": this.parentId,
"charId": this.charId,
};
return CustomBuffs.find(selector);
}
});
Template.customBuffEditList.events({
"tap #addBuffButton": function(event, instance){
if (!_.isBoolean(this.enabled)) {
this.enabled = true;
}
const customBuffId = CustomBuffs.insert({
name: this.name || "New Buff",
charId: this.charId,
parent: {
id: this.parentId,
collection: this.parentCollection,
},
});
pushDialogStack({
template: "customBuffEdit",
data: {customBuffId},
element: event.currentTarget,
returnElement: () => instance.find(`tr.buff[data-id='${customBuffId}']`),
});
},
});
Template.customBuffEditListItem.events({
"tap .edit-buff": function(event, template){
pushDialogStack({
template: "customBuffEdit",
data: {customBuffId: this.buff._id},
element: event.currentTarget.parentElement.parentElement,
});
},
});

View File

@@ -1,8 +0,0 @@
<template name="customBuffView">
<div class="flex">{{buff.name}}</div>
<div class="flex">
{{#if canEditCharacter buff.charId}}
<paper-button class="apply-buff-button">Apply{{toSelf}}</paper-button>
{{/if}}
</div>
</template>

View File

@@ -1,82 +0,0 @@
const applyBuff = function(targetId, buff) {
var parent = global[buff.parent.collection].findOne(buff.parent.id);
//insert new buff
newBuffId = Buffs.insert({
charId: targetId,
name: buff.name,
description: buff.description,
lifeTime: {total: buff.lifeTime.total},
type: "custom",
appliedBy: buff.charId,
appliedByDetails: {
name: parent.name,
collection: buff.parent.collection,
},
});
//insert children
Attacks.find({"parent.id": buff._id}).forEach(function(doc){
temp = _.clone(doc);
temp.parent.id = newBuffId;
temp.parent.collection = "Buffs";
delete temp._id;
Attacks.insert(temp);
});
Effects.find({"parent.id": buff._id}).forEach(function(doc){
temp = _.clone(doc);
temp.parent.id = newBuffId;
temp.parent.collection = "Buffs";
delete temp._id;
Effects.insert(temp);
});
Proficiencies.find({"parent.id": buff._id}).forEach(function(doc){
temp = _.clone(doc);
temp.parent.id = newBuffId;
temp.parent.collection = "Buffs";
delete temp._id;
Proficiencies.insert(temp);
});
let target;
if (targetId == buff.charId) {
target = "self";
} else {
target = Characters.findOne(targetId) || {};
target = target && target.name || "target"
}
GlobalUI.toast(`${buff.name || "Buff"} applied to ${target}`);
};
Template.customBuffView.helpers({
toSelf: function() {
if (this.buff.target === "self") {
return " to self";
} else {
return "";
}
}
});
Template.customBuffView.events({
"click .apply-buff-button": function(){
if (this.buff.target !== "self") {
pushDialogStack({
template: "applyBuffDialog",
data: {buff: this.buff},
element: event.currentTarget,
callback: (targetId) => {
if (!targetId) return;
applyBuff(targetId, this.buff);
},
});
} else {
var targetId = this.buff.charId;
applyBuff(targetId, this.buff);
}
},
});

View File

@@ -1,14 +0,0 @@
<template name="customBuffViewList">
{{#if buffs.count}}
<div class="buffs">
<div class="paper-font-title" style="margin-bottom: 8px;">
Buffs
</div>
{{#each buff in buffs}}
<div class="layout horizontal center">
{{> customBuffView buff=buff}}
</div>
{{/each}}
</div>
{{/if}}
</template>

View File

@@ -1,9 +0,0 @@
Template.customBuffViewList.helpers({
buffs: function(){
var selector = {
"parent.id": this.parentId,
"charId": this.charId,
};
return CustomBuffs.find(selector);
}
});

View File

@@ -1,27 +0,0 @@
<template name="characterSettings">
{{#with character}}
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="feedback flex">
<app-header fixed effects="waterfall">
<app-toolbar>
<div main-title>Character Settings</div>
</app-toolbar>
</app-header>
<div class="form flex">
<paper-toggle-button id="hideSpellcasting" checked={{settings.hideSpellcasting}}>
Hide Spells tab
</paper-toggle-button>
<paper-toggle-button id="variantEncumbrance" checked={{settings.useVariantEncumbrance}}>
Use variant encumbrance
</paper-toggle-button>
<paper-toggle-button id="swapStatAndModifier" checked={{settings.swapStatAndModifier}}>
Swap stats and modifiers on Stats page
</paper-toggle-button>
</div>
</app-header-layout>
<div class="buttons layout horizontal end-justified">
<paper-button class="doneButton"> Done </paper-button>
</div>
</div>
{{/with}}
</template>

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