Compare commits

...

1279 Commits

Author SHA1 Message Date
Stefan Zermatten
11a2851ac4 Fixed slots with computed expected quantity not hiding when full 2021-03-10 14:51:38 +02:00
Stefan Zermatten
313382fb82 Fixed library subscription issues 2021-03-10 14:40:14 +02:00
Stefan Zermatten
b9ae337a64 Merge branch 'version-2-dev' of https://github.com/ThaumRystra/DiceCloud into version-2-dev 2021-03-02 14:32:08 +02:00
Stefan Zermatten
4dc0a6159b Animated log entries 2021-03-02 14:32:05 +02:00
Stefan Zermatten
e00dfe1532 Changed the color of the log background 2021-03-02 14:31:35 +02:00
Stefan Zermatten
28e1fcabd5 Fixed damage properties by name failing if no properties were found 2021-03-02 14:10:14 +02:00
Stefan Zermatten
2c0496b44b Fixed properties not being made inactive by toggles 2021-03-02 13:56:53 +02:00
Stefan Zermatten
89adda60ec Reworked single page libraries to be more in line with the library view 2021-03-02 13:05:38 +02:00
Stefan Zermatten
8c3710cda3 Started work on single page libraries 2021-03-02 00:24:54 +02:00
Stefan Zermatten
b501b9d830 Fixed crash in skill calculation when level is overridden by an attribute 2021-03-01 18:40:55 +02:00
Stefan Zermatten
574f8373e7 Fixed crash when indexing a non-array node, added more array node errors 2021-03-01 14:47:46 +02:00
Stefan Zermatten
a7ecdecec1 Prevented contextual variables #type from being written to creature variable list 2021-03-01 14:22:03 +02:00
Stefan Zermatten
0aa59a4bfc Fixed creature not recomputing correctly when weight carried changes 2021-03-01 14:15:21 +02:00
Stefan Zermatten
8f0ff3245e Fixed containers still carrying their own weight if their contents are weightless and they aren't carried 2021-03-01 14:15:01 +02:00
Stefan Zermatten
9a2d10b7ed Fixed new library button hiding and not coming back 2021-03-01 14:08:12 +02:00
Stefan Zermatten
a8aa1923a8 Fixed spells having a stray deativatedBySelf flag 2021-03-01 14:01:34 +02:00
Stefan Zermatten
57fa162c89 Fixed stray errors from unepexted types 2021-03-01 13:37:19 +02:00
Stefan Zermatten
4d548c901c Ensured property exists before attempting to damage it 2021-03-01 13:32:46 +02:00
Stefan Zermatten
a97be2f93a Made constants work in calculations performed after recomputation 2021-03-01 13:27:48 +02:00
Stefan Zermatten
1276f872a0 Removed unused function 2021-03-01 12:11:22 +02:00
Stefan Zermatten
7daab97297 Made toggles function properly when nested under inactive properties and each other 2021-03-01 11:55:43 +02:00
Stefan Zermatten
2e3704d096 Prevented resources from writing unchanged data to the database 2021-03-01 11:42:50 +02:00
Stefan Zermatten
7283a27727 Constants should now respect toggles 2021-03-01 11:42:23 +02:00
Stefan Zermatten
3517636b8b Reworked toggles, again, to try and catch more edge cases. Made toggles set the inactive status of their property children in the compute step instead of the inactive denormalisation step 2021-03-01 11:41:59 +02:00
Stefan Zermatten
e617ef9b75 Merge branch 'version-2' into version-2-dev 2021-03-01 10:18:55 +02:00
Stefan Zermatten
cd45ae1442 Fixed buffs not recomputing correctly because of inactive properties not being activated 2021-03-01 10:07:24 +02:00
Stefan Zermatten
bcedd548c7 Fixed: If usesUsed was undefined, usesLeft of an action was NaN 2021-03-01 10:06:31 +02:00
Stefan Zermatten
dc53e38efe Libraries only fetch their data whene expanded 2021-02-27 10:49:10 +02:00
Stefan Zermatten
e381b3b24d Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2021-02-26 09:48:22 +02:00
Stefan Zermatten
111d971bc2 Added attacks and actions to stats tab quick insert 2021-02-26 09:48:18 +02:00
Stefan Zermatten
bf4ce4f9f7 Hotfix: Adding properties to the tree, type selection fixed 2021-02-25 19:43:17 +02:00
Stefan Zermatten
2a983b0a94 User accounts can now be deleted with some UI to prevent accidental deletion 2021-02-25 14:28:51 +02:00
Stefan Zermatten
a5460bba0b Added floating action button to add properties directly to the sheet 2021-02-25 12:37:32 +02:00
Stefan Zermatten
df361236f5 Hotfix: Containers total weight now showing correctly on inventory tab 2021-02-24 15:18:32 +02:00
Stefan Zermatten
e1d670fe9f Fixed: buffs 2021-02-24 15:05:53 +02:00
Stefan Zermatten
1e9f0515e5 Contents that are weightless are now summed and stored on the container 2021-02-24 14:22:52 +02:00
Stefan Zermatten
0404020335 Added weights and content weight to containers UI 2021-02-24 14:07:20 +02:00
Stefan Zermatten
c248d8f4a0 Weight carried, Net worth, and Attunement implemented and exposed in UI 2021-02-24 13:41:30 +02:00
Stefan Zermatten
8d95da8b7a Fixed a bug where certain base values would be strings instead of numbers in effect aggregators 2021-02-24 11:58:04 +02:00
Stefan Zermatten
e11ab39864 Added tableLookup function 2021-02-24 11:57:40 +02:00
Stefan Zermatten
331fcef9ad Fixed: Error message when focus grabbing element is missing on form 2021-02-24 10:06:25 +02:00
Stefan Zermatten
7e3bff9677 Show creature milestone level and xp if creature has both 2021-02-24 10:05:11 +02:00
Stefan Zermatten
1b650b26b6 Fixed: using creature stats like XP in calculations 2021-02-24 10:01:02 +02:00
Stefan Zermatten
5925605962 Fixed property edit buttons no longer get pushed by long property name 2021-02-24 09:52:51 +02:00
Stefan Zermatten
dee1265b69 Fixed: Inline calculations in libarries now display as expected 2021-02-24 09:46:52 +02:00
Stefan Zermatten
3d3ec3bcf2 Increaed number of slot fillers loaded by the slot fill dialog to 20 2021-02-24 09:23:55 +02:00
Stefan Zermatten
dce2c92516 Added attack roll bonus and dc to spell list. Use them in spells with #spellList.dcResult and #spellList.attackRollBonusResult 2021-02-23 15:21:20 +02:00
Stefan Zermatten
0fe2780983 Added property viewer for Toggle properties 2021-02-23 15:07:07 +02:00
Stefan Zermatten
e126cdd3cb Added property viewer for slot filler 2021-02-23 14:59:53 +02:00
Stefan Zermatten
d69ada0db4 Slot quantity is now a computed value, added property viewer for slots 2021-02-23 14:53:47 +02:00
Stefan Zermatten
858915b25b Added viewer for Saving Throw properties 2021-02-23 14:38:20 +02:00
Stefan Zermatten
d10a7eca14 Added viewer for Roll properties 2021-02-23 14:29:48 +02:00
Stefan Zermatten
671d17018c Added a viewer for Constant properties 2021-02-23 14:23:00 +02:00
Stefan Zermatten
f2883d320f Improved Attribute damage viewer 2021-02-23 13:59:26 +02:00
Stefan Zermatten
aad0c7249e Removed stray log to console 2021-02-23 12:47:34 +02:00
Stefan Zermatten
612fcca68c Only split properties accross targets if there are targets 2021-02-22 14:30:50 +02:00
Stefan Zermatten
12939c46de made saves walk children when not targeted at self 2021-02-22 14:28:38 +02:00
Stefan Zermatten
3801b17fde Attacks can now critical hit. criticalHitTarget overrides the roll required 2021-02-22 14:07:12 +02:00
Stefan Zermatten
88133a2fa3 Saving throws now work in actions 2021-02-22 12:38:21 +02:00
Stefan Zermatten
d00eedac19 Rolls now work in actions 2021-02-22 11:55:08 +02:00
Stefan Zermatten
6571fb860a Toggles now work in actions to make choices based on action context 2021-02-22 11:36:30 +02:00
Stefan Zermatten
8148f4d701 Fixed: Library nodes are published in order to prevent disordered building of the tree 2021-02-21 17:05:09 +02:00
Stefan Zermatten
523c34b719 Fixed: Slots that use conditions now only hide on falsey value (false, 0, '') 2021-02-21 17:01:24 +02:00
Stefan Zermatten
e833fba870 Fixed: bug that stopped buffs being deleted 2021-02-21 16:35:35 +02:00
Stefan Zermatten
f3e191c12e Fixed: Inserting properties to the tree now animate correctly to the inserted property 2021-02-20 16:00:40 +02:00
Stefan Zermatten
33415275a3 Item tiles are now smaller in the inventory view 2021-02-20 15:53:58 +02:00
Stefan Zermatten
3b1151d987 Fixed notes without summaries are no longer oversized 2021-02-20 15:52:15 +02:00
Stefan Zermatten
4288f98f7c Fixed: stats with no ability selected have an ability modifier of 0 instead of NaN 2021-02-20 15:50:25 +02:00
Stefan Zermatten
1a2ef8a4a2 Fixed: markdown images no longer overflow their container width 2021-02-20 15:45:45 +02:00
Stefan Zermatten
10e9a5faa8 Notes now show both summary and description in viewer 2021-02-20 15:41:30 +02:00
Stefan Zermatten
53594c0004 Fixed: items in containers not following tree order 2021-02-20 15:38:51 +02:00
Stefan Zermatten
e068675b46 Fixed and improved: Discord webhooks are working again with a new format 2021-02-20 15:27:20 +02:00
Stefan Zermatten
067f5df36e Fixed: Emptying the search bar in slot filler dialog now correctly clears the search 2021-02-20 10:17:35 +02:00
Stefan Zermatten
6113d86059 Fixed typo in calling a function in Constants autovalue 2021-02-16 11:19:50 +02:00
Stefan Zermatten
e3862bcdd9 Fixed constants autovalue 2021-02-16 10:49:18 +02:00
Stefan Zermatten
299f5a06dd Moved inline cacultion regex to a constant to ensure constistency 2021-02-16 10:14:26 +02:00
Stefan Zermatten
2776850311 Fixed missing import 2021-02-12 11:45:32 +02:00
Stefan Zermatten
3e6221309e Notes now have a summary and a description, some data migration my be needed 2021-02-12 11:44:03 +02:00
Stefan Zermatten
a078ce3d5d Description fields should now show calculation errors 2021-02-12 11:43:13 +02:00
Stefan Zermatten
b9e0475d07 Markdown now follow Github Flavor Markdown, including single line breaks 2021-02-12 11:11:12 +02:00
Stefan Zermatten
2b345c1f77 Improved error handling for most calculations 2021-02-12 11:00:44 +02:00
Stefan Zermatten
fed87f0a84 Fixed: items and spells should no longer be draggable when you don't have edit permission 2021-02-12 09:41:24 +02:00
Stefan Zermatten
b116be1238 Fixed flickering when inserting properties from library by ensuring consistent ID generation 2021-02-12 00:43:56 +02:00
Stefan Zermatten
ae373330ab Improved slot fill dialog UI, abandoned column layout in favour of wrapping flex rows 2021-02-12 00:18:29 +02:00
Stefan Zermatten
dcb535c84e Moved slot filler search to server side, limited docs in subscription 2021-02-11 22:09:43 +02:00
Stefan Zermatten
8c477ad4b1 Fixed constants not being found when used as the only thing in a calculation 2021-02-11 18:01:30 +02:00
Stefan Zermatten
eb2dd3bba1 Fixed computation dependency aggregation broken by refactoring 2021-02-11 16:27:22 +02:00
Stefan Zermatten
dc4808c70a fixed snackbars being blank 2021-02-11 16:20:33 +02:00
Stefan Zermatten
16c8c1db81 Improved descriptions in log entries 2021-02-11 16:16:22 +02:00
Stefan Zermatten
92a5c1e6c3 Improved log entries for actions 2021-02-11 16:08:31 +02:00
Stefan Zermatten
439eadf079 Condensed logs to a single card per action 2021-02-11 15:48:23 +02:00
Stefan Zermatten
d7083cf242 Hid unused cards on the stats tab 2021-02-11 13:48:10 +02:00
Stefan Zermatten
3af5e820ca Stopped inactive effects showing up in attributes 2021-02-11 13:38:34 +02:00
Stefan Zermatten
20aaab4dea Hid inactive notes 2021-02-11 13:14:31 +02:00
Stefan Zermatten
81cdf282ea Stopped inactive properties from showing computed inline fields, since they are not recomputed while inactive 2021-02-11 13:12:35 +02:00
Stefan Zermatten
3313ed0297 Added constants to the UI and Computation Engine 2021-02-11 13:03:31 +02:00
Stefan Zermatten
25fd5c18e8 Fixed casting broken by refactoring 2021-02-11 10:13:35 +02:00
Stefan Zermatten
5b9bb6e4bc Fixed spending resources for actions broken by refactoring 2021-02-11 10:13:23 +02:00
Stefan Zermatten
74370f6fec Performance optimization: Removed creature document from injection to prevent uneccessary Vue re-rendering 2021-02-11 10:04:28 +02:00
Stefan Zermatten
565ddccba6 fixed broken import 2021-02-04 16:55:50 +02:00
Stefan Zermatten
4ea4348a02 Now writing partial recalculations to creature variables 2021-02-04 16:52:26 +02:00
Stefan Zermatten
280f30dab5 Improved dependencies-only recalculations and fixed many calculation bugs 2021-02-04 16:16:51 +02:00
Stefan Zermatten
6d1e3f078c Optimised when certain recompute functions are called to prevent unccessary work 2021-02-04 13:59:08 +02:00
Stefan Zermatten
326d1bd165 Refactored computation into folders 2021-02-04 13:33:20 +02:00
Stefan Zermatten
87fa941f63 Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2021-02-04 11:38:55 +02:00
Stefan Zermatten
449a4fba7d Refactored creature property methods into separate documents, might have broken a lot of things 2021-02-04 11:38:29 +02:00
Stefan Zermatten
9ff096ec0f Added modulo operator 2021-02-03 09:33:00 +02:00
Stefan Zermatten
f9f0186d95 Fixed error where parser was not creating accessor nodes correctly 2021-02-03 00:39:11 +02:00
Stefan Zermatten
60ea545ee9 Started implementing constant property 2021-02-02 16:36:23 +02:00
Stefan Zermatten
aaa5d0b63b Allowed effects and calculations to target nearest ancestors of #type 2021-02-02 16:11:59 +02:00
Stefan Zermatten
69c72e0987 Fixed parenthesis being discarded from compiled calculations in cases where they should not be 2021-02-02 15:13:49 +02:00
Stefan Zermatten
a6df4df534 Fixed some fields not storing strings when compiling calculations 2021-02-02 15:07:31 +02:00
Stefan Zermatten
8b8f9bea6f Removed character variables from creatures injected into character sheet as context 2021-01-31 20:13:45 +02:00
Stefan Zermatten
e7a27e4b83 Fixed hit dice breaking long rests 2021-01-31 19:45:14 +02:00
Stefan Zermatten
e14e875c42 Fixed conMod not being signed in hit dice tiles 2021-01-31 19:43:09 +02:00
Stefan Zermatten
a7898bfe4b Fixed props not having a default array for embedded calculations 2021-01-31 19:42:49 +02:00
Stefan Zermatten
aee899e181 Removed all UI computations from viewers and components 2021-01-31 18:42:17 +02:00
Stefan Zermatten
a5284bf6e8 Made 'always prepared' spells show up in casting list 2021-01-30 11:22:29 +02:00
Stefan Zermatten
0ea3f7a975 Migrated fields to embedded property calculations 2021-01-29 13:04:53 +02:00
Stefan Zermatten
1167538977 Denormalised inline calculations to property documents, needs to be referenced by UI still 2021-01-29 12:29:01 +02:00
Stefan Zermatten
9c799e3dc9 Added search to slot fill dialog 2021-01-28 17:09:37 +02:00
Stefan Zermatten
2bf749c4f1 Fixed: Layout of slot fill dialog is no longer broken by tall images, background is now the correct color 2021-01-28 16:48:33 +02:00
Stefan Zermatten
db83d5f82a Fix: Tabs and tab headers no longer lose sync when changing characters 2021-01-28 16:17:03 +02:00
Stefan Zermatten
d596061fa8 Fixed: Attributes that aren't ability scores now have their modifiers removed correctly 2021-01-28 16:05:47 +02:00
Stefan Zermatten
78efe639ed Fixed: Zero effect passive bonus now causes passive skill value to show. 2021-01-28 16:01:58 +02:00
Stefan Zermatten
e5bde38745 Removed console timers from recompute function: it's not the primary slowdown 2021-01-28 15:00:36 +02:00
Stefan Zermatten
83f2047dbe Replaced expensive getActiveProperties with cheaper filter by inactive field 2021-01-28 14:29:10 +02:00
Stefan Zermatten
fc03097ed8 Patreon access is now given by tier instead of by price paid 2021-01-28 12:15:45 +02:00
Stefan Zermatten
252ac23391 Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2021-01-27 22:25:19 +02:00
Stefan Zermatten
531ddce6a0 Added dependency tracking to computations for future optimization effort 2021-01-27 22:24:28 +02:00
Stefan Zermatten
3cdeb73c30 Stopped removed items from showing up in inventory containers 2021-01-25 12:38:46 +02:00
Stefan Zermatten
c780c11e3f Improved spell slot casting dialog with search, filters, spell details 2021-01-22 14:09:23 +02:00
Stefan Zermatten
100c93b5ae Stopped dialog animation mocking transparent elements' background color 2021-01-22 12:20:33 +02:00
Stefan Zermatten
a4e6dd1d66 Added spellcasting to the stats page, click the icon next to a spell slot to cast 2021-01-19 16:10:34 +02:00
Stefan Zermatten
1b3b6362f7 Exposed spellSlotLevelValue to creature variables store 2021-01-19 12:07:40 +02:00
Stefan Zermatten
bdf4074e3c Added first pass at denormalizing inventory data, needs testing and integration 2021-01-15 17:41:06 +02:00
Stefan Zermatten
9492b2d8b8 Characters now start with a slot for a base. New characters start on the character tab with the build dialog open. 2021-01-14 12:28:51 +02:00
Stefan Zermatten
27f1f4e720 Fixed a bug with dialog animations hiding buttons by failing to clean up 2021-01-12 13:51:36 +02:00
Stefan Zermatten
d63b8c835d Fixed bug in last release where unlimited slots were always hidden on hide when full 2021-01-12 13:51:11 +02:00
Stefan Zermatten
85f3881935 Removed alert about data loss, most data structures are stable and it's being alarmist 2021-01-12 13:40:02 +02:00
Stefan Zermatten
5e4299e6db removed stray console logging 2021-01-12 13:24:08 +02:00
Stefan Zermatten
0e663f36db Slot fillers that count as more than one slot now update the space left correctly.
Slot fillers removed from a library can no longer be added to a slot.
If a slot has limited space left, this will be reflected on the error 
finding slots message
2021-01-12 13:22:48 +02:00
Stefan Zermatten
de9ea5922c Fixed bugs when a spell list does not have limit on prepared spells 2021-01-12 13:10:36 +02:00
Stefan Zermatten
a2cfe43e74 Disabled the direct damage input in the attribute form: it can't be edited anyway 2021-01-12 13:04:25 +02:00
Stefan Zermatten
026c11c13b Made sure attributes show their currentValue instead of Value 2021-01-12 13:03:33 +02:00
Stefan Zermatten
403f2663c2 Fixed bugs with item display, equipment will now automatically move to the first property with the 'equipment' tag, carried items will move to the first property with the 'carried' tag 2021-01-12 12:54:02 +02:00
Stefan Zermatten
28c042343e All children of infinite slots will now hide when "hide when full" is active 2021-01-12 10:36:07 +02:00
Stefan Zermatten
3f116875a1 Children of slots now display in the correct order 2021-01-12 10:35:37 +02:00
Stefan Zermatten
ae5b4b7d5c Made inactive toggle decendants specifically included when recomputing active properties 2021-01-11 22:03:54 +02:00
Stefan Zermatten
75835d74f6 Merge pull request #259 from Ganonsmasher/version-2
Found a fix for toggles.
2020-12-17 17:19:54 +02:00
Ganonsmasher
583b652fc4 Found a fix for toggles.
It doesn't fix the problem of their property being absent on the stats page cards, but they can compute properly and disable their children with this.
2020-12-15 18:08:29 -05:00
Stefan Zermatten
df317a8942 Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud into version-2 2020-11-20 14:21:52 +02:00
Stefan Zermatten
ff0e9b1ff9 Added Lord of Junk to Patreon Paragons 2020-11-20 14:13:47 +02:00
Stefan Zermatten
fa24430a7f Fixed parsing of variable names with numbers and stacked dice rolls like dd8-> 1d(1d8) 2020-11-13 10:04:37 +02:00
Stefan Zermatten
fde2f821e7 Fixed parser not handling whitespace 2020-11-12 21:44:08 +02:00
Stefan Zermatten
827430c987 Fixed edit permission errors for some creature prop methods 2020-11-12 21:25:48 +02:00
Stefan Zermatten
2a1aa02e97 Added true and false keywords, fixed grammar ambiguity in if statements 2020-11-12 15:11:24 +02:00
Stefan Zermatten
aeb347084f Fixed ambiguitiy in grammar caused by previous fixes 2020-11-12 13:47:10 +02:00
Stefan Zermatten
005bc162cb Fixed != not being matched because ! was matched first. Fixed presedence for & | and relational operators 2020-11-12 13:45:14 +02:00
Stefan Zermatten
9941d91bb8 Fixed != operator, separated == and ===, != and !== for strictness control 2020-11-12 13:44:01 +02:00
Stefan Zermatten
23c77690a1 Healing damage type now heals instead of damaging 2020-11-12 13:01:09 +02:00
Stefan Zermatten
525b528d9a Added attribute damage and self damage results to actions and log. 2020-11-12 12:57:48 +02:00
Stefan Zermatten
3917f63d5e Increased subscription rate limit to prevent infinitely spinning characters 2020-11-12 11:04:04 +02:00
Stefan Zermatten
bd056ab042 Improved subscription permissions, should now work as expected for public documents 2020-11-12 10:48:46 +02:00
Stefan Zermatten
cd84b2562a Fixed an error with finding deployed version SHA 2020-11-10 14:48:09 +02:00
Stefan Zermatten
c7436ffb1e Caught error where git couldn't be used to get version 2020-11-10 14:37:32 +02:00
Stefan Zermatten
be7d7f898f disabled short and log rest buttons if user has no edit permission 2020-11-10 14:22:09 +02:00
Stefan Zermatten
3024168e95 Replaced old parser with new parser 2020-11-10 14:07:22 +02:00
Stefan Zermatten
1f0678b50b Added not operator to the parser 2020-11-05 15:32:01 +02:00
Stefan Zermatten
4dad2c41e5 Updated parser to accept underscores in variable names 2020-11-05 14:50:44 +02:00
Stefan Zermatten
46385dd9b2 Build details with no slots hidden moved to a dialog 2020-11-05 14:27:01 +02:00
Stefan Zermatten
7cb65954b5 Added the ability to hide slots when full 2020-11-05 14:05:17 +02:00
Stefan Zermatten
749799d869 Denormalised slot fill total to database 2020-11-05 13:35:55 +02:00
Stefan Zermatten
3293dad671 Limited what fields are included when fetching recompute documents to improve performance 2020-11-05 12:59:48 +02:00
Stefan Zermatten
88df942c59 Fixed an error with missing identity details in patreon request 2020-11-05 12:59:26 +02:00
Stefan Zermatten
9722bbc667 Characters now recompute on subscribe if they haven't been computed in the current version 2020-11-04 14:27:31 +02:00
Stefan Zermatten
2fb0ba79c6 began work to get inactive state of properties denormalised 2020-11-03 15:57:14 +02:00
Stefan Zermatten
3f7ddd62fc Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud1 into version-2 2020-10-27 10:41:06 +02:00
Stefan Zermatten
227d6c5aae Markdown and calculations now supported in slot filler descriptions 2020-10-27 10:39:20 +02:00
Stefan Zermatten
147ef97576 Markdown now supported in slot filler descriptions 2020-10-27 10:34:01 +02:00
Stefan Zermatten
54806b0f3c Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud1 into version-2 2020-10-19 11:39:24 +02:00
Stefan Zermatten
1165158d46 Forced creatures to reorder their docs before recomputing 2020-10-19 11:39:21 +02:00
Stefan Zermatten
32de70cd45 Class levels now have a description field 2020-10-17 19:33:56 +02:00
Stefan Zermatten
68499e4de5 You can now click on properties filling slots to view their details dialog 2020-10-17 19:28:54 +02:00
Stefan Zermatten
3f4cb8e26b Added undo buttons for deleting properties off a creature 2020-10-17 19:10:37 +02:00
Stefan Zermatten
ebab41838c Used tree node views in slot fill selection 2020-10-17 16:56:23 +02:00
Stefan Zermatten
e8da7a6c17 Moved snackbars to their own store and component 2020-10-17 16:06:27 +02:00
Stefan Zermatten
46189c68df All property forms now allow tags 2020-10-17 13:42:24 +02:00
Stefan Zermatten
9fa997ed24 Started moving snackbars to vue store, still needs to be separated into its own module 2020-10-16 13:38:58 +02:00
Stefan Zermatten
e3bf6557ec fixed bug in dialog store 2020-10-16 10:35:51 +02:00
Stefan Zermatten
7aa3e5a217 Stringified errors from scheduled deletion 2020-10-16 10:23:34 +02:00
Stefan Zermatten
dc1b025090 Increased delete job frequency to 10 minutes 2020-10-16 10:00:07 +02:00
Stefan Zermatten
2e370a9884 Fixed removed slots not being hidden 2020-10-15 16:42:57 +02:00
Stefan Zermatten
384fa076f1 hotfix tags not filtering correctly 2020-10-15 16:12:32 +02:00
Stefan Zermatten
7922e30ddc Added tags to some properties. Added condition to class levels 2020-10-15 16:00:32 +02:00
Stefan Zermatten
1ba4f76763 Class levels can now have conditions 2020-10-15 15:57:19 +02:00
Stefan Zermatten
839f91c3b2 Fixed icons not going to dark mode when slot filling cards are selected 2020-10-15 15:30:11 +02:00
Stefan Zermatten
26567ce840 slot fill cards with pictures no longer get icons 2020-10-15 15:25:10 +02:00
Stefan Zermatten
2a729a4eca Made slot fill dialog a list of cards to leverage pretty slotFillers 2020-10-15 15:24:14 +02:00
Stefan Zermatten
ed17d9e2d2 Added slotfiller property type to increase control over slot filling 2020-10-15 14:54:58 +02:00
Stefan Zermatten
8e9405b5ad Allowed slots with unlimited children, improved slot ui text 2020-10-15 13:50:46 +02:00
Stefan Zermatten
7fc783dcad Removed recompute button 2020-10-15 13:50:14 +02:00
Stefan Zermatten
b15ad7e51a Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud1 into version-2 2020-10-15 13:49:49 +02:00
Stefan Zermatten
8a3d2474fc Merge pull request #254 from JoeZwet/version-2
fix: prevent discord mention exploit
2020-10-15 13:48:30 +02:00
Joe van der Zwet
09371e7d54 add requested changes 2020-10-16 00:47:32 +13:00
Joe van der Zwet
0776d33909 fix: prevent discord mention exploit 2020-10-16 00:36:32 +13:00
Stefan Zermatten
6e98d71c3c Improved slot UI look and feel 2020-10-15 13:00:29 +02:00
Stefan Zermatten
8f89f4b63f Ensured all subscriptions return empty arrays instead of errors or ready 2020-10-15 12:34:46 +02:00
Stefan Zermatten
c0070d017e Removed debugging code 2020-10-14 15:54:16 +02:00
Stefan Zermatten
51569592ab First implementation on Slots UI 2020-10-14 14:45:26 +02:00
Stefan Zermatten
d2cb86ac27 Fixed broken logging for actions 2020-10-14 11:33:25 +02:00
Stefan Zermatten
bde9183158 Log optimistic UI now fixed, rolls are now instant 2020-10-14 11:25:05 +02:00
Stefan Zermatten
0cc9e01754 Renamed, moved LogTab to CharacterLog 2020-10-14 09:37:00 +02:00
Stefan Zermatten
9856471202 Stopped log making toast if it's visible 2020-10-14 09:33:17 +02:00
Stefan Zermatten
4f77782a7a log messages are now aligned right 2020-10-13 13:53:30 +02:00
Stefan Zermatten
5f13aaa031 Fixed empty strings in log input 2020-10-13 13:44:06 +02:00
Stefan Zermatten
1321cf6a96 Moved log tab to right drawer 2020-10-13 13:42:18 +02:00
Stefan Zermatten
dee8249f61 Creature logs are now removed with creatures 2020-10-13 12:43:55 +02:00
Stefan Zermatten
0af0afc0d0 Discord webhooks now mirror character log 2020-10-13 12:42:02 +02:00
Stefan Zermatten
a104fc8a87 Fixing broken casing on file pt.2 2020-10-06 10:55:12 +02:00
Stefan Zermatten
46f452987f Fixing broken casing on file 2020-10-06 10:54:52 +02:00
Stefan Zermatten
a87cb1286a Improved custom rolls on log tab 2020-10-06 09:53:08 +02:00
Stefan Zermatten
844588cdbf Started adding text input to log tab 2020-09-30 16:24:33 +02:00
Stefan Zermatten
a6a96fc19f Started work on character log for rolls to be stored 2020-09-29 22:34:30 +02:00
Stefan Zermatten
75ab43da00 Started work on UI for rolling checks 2020-09-29 16:37:28 +02:00
Stefan Zermatten
df7000889b fixed security deps 2020-09-29 10:54:37 +02:00
Stefan Zermatten
65754dea80 removed damage multipliers from health bar card, it has its own card 2020-09-29 10:54:27 +02:00
Stefan Zermatten
30cca39e7c Merge branch 'version-2' of https://github.com/ThaumRystra/DiceCloud1 into version-2 2020-09-28 13:58:03 +02:00
Stefan Zermatten
5ad5c914fb Added gitignore file for renders 2020-09-24 19:00:28 +02:00
Stefan Zermatten
f27550362a Fixed various parser bugs, implemented unary operators 2020-09-18 22:13:12 +02:00
Stefan Zermatten
50f7977a60 Fixed patreon update write location 2020-09-18 20:37:16 +02:00
Stefan Zermatten
bc5c465a32 Started work on checks 2020-09-18 14:00:29 +02:00
Stefan Zermatten
c8ddf9d547 Added the ability to double all number of dice to roll using context 2020-09-18 12:24:08 +02:00
Stefan Zermatten
6570665c1e Added functions and ensured the context was being passed around correctly 2020-09-18 11:52:44 +02:00
Stefan Zermatten
06f17a6d33 Parser now uses context to store details of the computation 2020-09-18 10:14:53 +02:00
Stefan Zermatten
b69ad6c306 Removed unused parser code 2020-09-10 11:39:27 +02:00
Stefan Zermatten
5dec760452 Parser now works with variables passed into scope 2020-09-10 11:38:28 +02:00
Stefan Zermatten
ede4e1367d Continued work on parser, now calling functions and rolling correctly 2020-09-10 00:14:24 +02:00
Stefan Zermatten
81645df2a6 Lots of work on the parser including testing interface 2020-09-09 17:09:50 +02:00
Stefan Zermatten
445171ce80 Added preferences subheader to accounts page 2020-09-09 13:58:48 +02:00
Stefan Zermatten
dedab7b046 Added patreon tier refresh button, autorefresh tier on login daily 2020-09-09 13:55:21 +02:00
Stefan Zermatten
a5c16ba83a Overhauled inventory tab again. Closer in functionality to V1 2020-08-22 00:36:17 +02:00
Stefan Zermatten
46501f2759 Spells that aren't prepared no longer count as active properties 2020-08-21 16:34:52 +02:00
Stefan Zermatten
a6ed1004be Added Resistance, Vulnerability, and Immunity to the health bar card 2020-08-21 16:27:01 +02:00
Stefan Zermatten
8539356b9e Added UI to prepare spells 2020-08-21 16:04:31 +02:00
Stefan Zermatten
93db5e9288 Made library link readonly instead of disabled so it can be copied 2020-08-10 04:23:55 +02:00
Stefan Zermatten
b4cb91a892 Multiple libraries can now be opened, allowing library items to be moved 2020-08-10 04:19:54 +02:00
Stefan Zermatten
9c93747845 Fixed bug where array accessors were attempting to use the substitution engine prematurely 2020-08-10 04:14:53 +02:00
Stefan Zermatten
a51154e434 Prevented test webhooks being sent in production 2020-07-26 19:56:08 +02:00
Stefan Zermatten
1bde0db0ba Fixed features showing up when disabled by an ancestor 2020-07-26 19:53:07 +02:00
Stefan Zermatten
bc001202ec Fixed spell list tiles not being opaque 2020-07-26 19:47:44 +02:00
Stefan Zermatten
c7985af83b Continued work on tabletops, hidden from main app and all methods disallowed for non-admins 2020-07-26 19:45:07 +02:00
Stefan Zermatten
0f20cd4bd9 Implemented drag and drop on spells page 2020-07-26 19:39:50 +02:00
Stefan Zermatten
1ac01941c7 Fixed bug where multiple classes woudn't show up in persona tab 2020-07-26 16:43:26 +02:00
Stefan Zermatten
95d8d2cb9a Started work on tabletop view 2020-07-17 23:31:12 +02:00
Stefan Zermatten
47345b3694 Experimenting with webhooks. 2020-07-13 16:38:24 +02:00
Stefan Zermatten
308168791b Made dX rolls work as 1dX 2020-06-30 15:15:55 +02:00
Stefan Zermatten
7be4280508 Began implementing dice rolls in the maths parser 2020-06-30 14:40:20 +02:00
Stefan Zermatten
56f9e82326 Improved spells tab to be more in line with the v1 implementation 2020-06-29 14:52:47 +02:00
Stefan Zermatten
6ddea8a8ab Improved slot schema, added ui for slots 2020-06-29 14:15:49 +02:00
Stefan Zermatten
e1ddfb2cab Fixed a bug where registering a property without disabledByToggle breaks recompuation 2020-06-24 18:20:56 +02:00
Stefan Zermatten
d36d5b15d0 hide unused spell card 2020-06-24 18:17:35 +02:00
Stefan Zermatten
2af687361e Improved spell appearance in spell tab 2020-06-23 01:36:48 +02:00
Stefan Zermatten
e572807082 Attributes of type spell slot now store their slot level 2020-06-23 01:36:21 +02:00
Stefan Zermatten
c44aeac198 Added 'prepared' field to spells 2020-06-22 13:45:47 +02:00
Stefan Zermatten
757cf5c34b Fixed spells not being able to be inserted or editing in characters 2020-06-22 13:45:35 +02:00
Stefan Zermatten
8cbfec25b3 Added the setting to swap ability scores and modifiers to the account page 2020-06-22 13:22:53 +02:00
Stefan Zermatten
c4dc5895aa Relaxed rate limiting on icon search, improved error messaging 2020-06-22 00:20:40 +02:00
Stefan Zermatten
cffe0ee574 Added minimal UI to display applied buffs 2020-06-22 00:14:07 +02:00
Stefan Zermatten
ce51be7b8e moved proficiencies after actions on the stats tab 2020-06-21 23:57:19 +02:00
Stefan Zermatten
315073bd8e Refactored actions and let actions apply buffs to self 2020-06-21 23:54:51 +02:00
Stefan Zermatten
50b99ef54f Improved performance of adding library properties with many decendants 2020-06-21 23:24:07 +02:00
Stefan Zermatten
9b01f5fb45 Improved actions UI, Actions (including spells) can now have icons 2020-06-17 13:23:13 +02:00
Stefan Zermatten
389785f5db Fixed bug where library large screen view won't scroll 2020-06-17 13:22:48 +02:00
Stefan Zermatten
e1bfb173ab Overhauled action detail view 2020-06-16 13:51:58 +02:00
Stefan Zermatten
ecba587253 Fixed a bug with proficiency forms not editing proficiency correctly 2020-06-16 12:35:50 +02:00
Stefan Zermatten
3f540d0f14 Overhaul of character action components, actions now consume resources 2020-06-15 22:30:27 +02:00
Stefan Zermatten
dc18734d1f Backend work to support actions consuming their resources on use 2020-06-13 23:11:49 +02:00
Stefan Zermatten
1535e00093 Denormalized some calculations into recomputation step 2020-06-07 21:08:53 +02:00
Stefan Zermatten
5198c655e9 Added subscription rate limiting 2020-06-06 14:30:15 +02:00
Stefan Zermatten
8d41643136 Increased damage property rate limit to 4/s 2020-06-06 14:25:23 +02:00
Stefan Zermatten
ea8d036c72 Added rate limiting to all methods 2020-06-06 14:23:13 +02:00
Stefan Zermatten
93d566e263 Exposed methods and publications to http requests, changed method names 2020-06-06 12:31:07 +02:00
Stefan Zermatten
b4da32f9ab Fixed soft removed documents never getting permanently removed 2020-06-05 23:08:31 +02:00
Stefan Zermatten
986fe8fd93 Added an autofocus field to most forms 2020-06-05 22:39:21 +02:00
Stefan Zermatten
dd4596851e Improved class level viewer and tree node view 2020-06-05 22:25:22 +02:00
Stefan Zermatten
bc3fc9574a Added loading and empty state to experience list 2020-06-05 22:20:40 +02:00
Stefan Zermatten
db1ae5db3d Iterated on XP system 2020-06-05 21:48:28 +02:00
Stefan Zermatten
d1e7eb2fa0 Added basic XP system 2020-06-05 16:14:26 +02:00
Stefan Zermatten
efb8b87a2d Alphabetized properties by displayed name 2020-05-31 22:39:15 +02:00
Stefan Zermatten
b04b915c7b Removed stray logging 2020-05-31 22:36:27 +02:00
Stefan Zermatten
21b823f85c Dark mode now with 20% more dark 2020-05-31 22:25:04 +02:00
Stefan Zermatten
4631579181 Character toolbar now correctly uses dark and light text where appropriate 2020-05-31 22:22:42 +02:00
Stefan Zermatten
edf3920e84 Character sheet toolbars now match the color of the character 2020-05-31 22:16:38 +02:00
Stefan Zermatten
fb91fd12df Set up custom icons for most properties 2020-05-31 21:03:45 +02:00
Stefan Zermatten
19f4735412 Icon search field now focuses when the menu is opened 2020-05-31 19:18:49 +02:00
Stefan Zermatten
fb2f1efa72 Property insert forms now have color selectors 2020-05-31 19:00:32 +02:00
Stefan Zermatten
f7ee09470e Improved container and item forms and viewers 2020-05-31 18:50:00 +02:00
Stefan Zermatten
a5c42fea19 Made custom svg icons work anywhere a regular icon would work 2020-05-31 18:49:46 +02:00
Stefan Zermatten
8f81614294 Weight and value are no longer required on containers 2020-05-31 15:59:37 +02:00
Stefan Zermatten
c56cebc652 Fixed dark/light font color swapping not working in dark mode 2020-05-31 15:58:21 +02:00
Stefan Zermatten
d24fb5661d re-enabled computation on client side for optimistic UI 2020-05-30 23:56:55 +02:00
Stefan Zermatten
4bdc254627 Improved item viewer significantly, including increment button. 2020-05-30 23:36:27 +02:00
Stefan Zermatten
db652ac47f Update paragon avatar 2020-05-30 19:28:54 +02:00
Stefan Zermatten
32c9283569 Library items can now correctly store icons 2020-05-30 18:19:57 +02:00
Stefan Zermatten
060c44f384 Added svg icons, currently only for items 2020-05-30 18:04:48 +02:00
Stefan Zermatten
8138cd98f1 Added quantities to item tree views 2020-05-30 12:52:15 +02:00
Stefan Zermatten
5195e3fad5 Made health bar input accept negative sign from copy-paste and mobile (hopefully) 2020-05-30 12:45:11 +02:00
Stefan Zermatten
eb97a98644 removed dead reference to reset multipliers 2020-05-30 12:34:00 +02:00
Stefan Zermatten
51845c62a7 Skill base values now work in a way consistent with attribute base values 2020-05-30 12:32:05 +02:00
Stefan Zermatten
56cd48da9d Fixed health bars not hiding 2020-05-29 00:38:12 +02:00
Stefan Zermatten
298f659829 Fixed broken import 2020-05-28 23:48:01 +02:00
Stefan Zermatten
b99d1a00f5 Fixed small issues with hit dice on long rest. rests trigger recomputations now 2020-05-28 23:43:03 +02:00
Thaum Rystra
15ad8b1f5d Added short and long rest buttons, closes #87 2020-05-28 23:17:25 +02:00
Thaum Rystra
d4804e5292 Made minimum variable name 2 characters long 2020-05-28 21:26:31 +02:00
Thaum Rystra
36c23e1eb5 Made hiding stats that aren't targeted by effects or proficiencies an option 2020-05-28 21:06:40 +02:00
Thaum Rystra
9236f3e477 Added calculation errors to attributes and toggles 2020-05-28 20:33:08 +02:00
Thaum Rystra
cd413ba64f Added icon for set effects 2020-05-28 20:17:16 +02:00
Thaum Rystra
2c671acf72 Made sure effects without calculations don't have computed results 2020-05-28 20:14:19 +02:00
Thaum Rystra
44e726417e Convert mathjs objects to strings in evaluations 2020-05-28 20:10:33 +02:00
Thaum Rystra
7f2401da81 Referencing a missing variable in an effect now returns zero, not an error 2020-05-28 19:58:52 +02:00
Thaum Rystra
d31f980002 Added paragon's title 2020-05-28 17:25:44 +02:00
Thaum Rystra
4c8512af80 Rounding only occurs on numbers, preventing uneccessary type casting of attribute values 2020-05-28 16:06:00 +02:00
Thaum Rystra
edf68b1355 Properties in dropdowns are sorted by order again, rather than name 2020-05-28 15:59:04 +02:00
Thaum Rystra
868b9e11fa Added 'set' operation to effects, it overrides all other numerical effects 2020-05-28 15:58:48 +02:00
Thaum Rystra
14f5c3e797 improved field naming for damage multiplier tag targeting 2020-05-28 15:47:02 +02:00
Thaum Rystra
66e25c53d0 Fixed paragon's avatar image 2020-05-28 15:46:39 +02:00
Thaum Rystra
7a75d34246 Added healing damage type 2020-05-28 15:41:46 +02:00
Thaum Rystra
70a6c817cb Organised images, added about page, tweaked home page 2020-05-28 15:27:55 +02:00
Thaum Rystra
56879f1911 Removing a property in the character sheet tree now unselects that property 2020-05-28 13:03:35 +02:00
Thaum Rystra
6d12bcb063 Public libraries no longer require login to view 2020-05-28 13:00:03 +02:00
Thaum Rystra
1c26b7717c Fixed saving throw fields that weren't working, added name to saving throws 2020-05-28 12:29:41 +02:00
Thaum Rystra
a41b267364 Use embedded property dialog in tree tab. Colors for creature properties 2020-05-25 19:36:14 +02:00
Thaum Rystra
dfb144b8dc Added color picking to library properties 2020-05-25 19:09:55 +02:00
Thaum Rystra
2859bf0e00 Added fade transition to library dialog 2020-05-25 18:41:22 +02:00
Thaum Rystra
469822d4d7 In organize mode, new library properties get placed under the selected node 2020-05-25 18:33:38 +02:00
Thaum Rystra
c7de96c8c3 Added "move" button to library property menu 2020-05-25 18:15:35 +02:00
Thaum Rystra
f7cbee27f9 Made selecting a property from a library use the mobile friendly library 2020-05-25 17:39:25 +02:00
Thaum Rystra
b61dd6e81a Added maximum length of ancestors array 2020-05-25 17:25:49 +02:00
Thaum Rystra
add0cac31d Added "duplicate" option to library properties 2020-05-25 17:23:36 +02:00
Thaum Rystra
e9c643699c Made tab swiping sync with the tab list 2020-05-25 17:07:38 +02:00
Thaum Rystra
3ec0f9500c Overhauled library UI to work on small screens 2020-05-25 16:43:28 +02:00
Thaum Rystra
a55c1382b1 Fixed skills not computing below zero 2020-05-24 04:30:14 +02:00
Thaum Rystra
7571806cd0 Made user profiles optional 2020-05-23 12:07:42 +02:00
Thaum Rystra
afa641a290 made ids optional in users publication 2020-05-21 16:52:36 +02:00
Thaum Rystra
81131ddb9f Allowed non-patreons to view, but not edit, sheets and libraries 2020-05-21 16:50:06 +02:00
Thaum Rystra
7a442d8fb9 Improved publications to be reactive to permission changes 2020-05-21 15:13:30 +02:00
Thaum Rystra
26d836767b Fixed some broken forms 2020-05-21 14:57:35 +02:00
Thaum Rystra
c4a52ca744 Display an error if the character can't be found or viewed 2020-05-21 13:36:59 +02:00
Thaum Rystra
b640ce457f Removed unused story files 2020-05-21 12:49:40 +02:00
Thaum Rystra
13a0d66433 Disabled various buttons when the user doesn't have edit permission 2020-05-21 12:47:02 +02:00
Thaum Rystra
47aad6d186 Added UI to unshare a view-only character with yourself 2020-05-20 16:52:05 +02:00
Thaum Rystra
32eb85a099 Refactored all forms to disable all fields without edit permission 2020-05-20 16:40:47 +02:00
Thaum Rystra
048b150c88 Significantly improved performance of interacting with large library trees 2020-05-19 01:28:29 +02:00
Thaum Rystra
65d367942e Got cards to behave themselves in columns and not overflow width 2020-05-19 01:03:18 +02:00
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
Stefan Zermatten
41c90bb69f Updated Meteor 2018-08-23 11:45:38 +02:00
Stefan Zermatten
68432541db Merge pull request #168 from Frogvall/master
Adding advantage/disadvantage arrows to initiative stat card
2018-08-23 11:34:02 +02:00
Frogvall
c417c45db1 Cleaned up a bit 2018-06-20 14:33:34 +02:00
Frogvall
3f3caf63e4 Add css and js implementation of previously naïve approach
Bonus: Learned some meteor :)
2018-06-20 14:25:18 +02:00
Jonas
49c5a1fcb1 Added adv class to skills in stat cards
Added arrows to initiative, for example
2018-06-18 00:51:20 +02:00
Stefan Zermatten
9fb37148fa Merge pull request #167 from mommothazaz123/master
Add raw character JSON output, improve ratelimiter rules of API
2018-06-08 09:26:52 +02:00
Andrew Zhu
a67d7fb4ea Merge branch 'master' into master 2018-06-07 02:06:34 -07:00
Andrew Zhu
8e0f19742b fix some branch conflicts 2018-06-07 02:03:12 -07:00
Andrew Zhu
216e502c8a add permission check to JSONcharacter 2018-06-07 01:38:29 -07:00
Andrew Zhu
1a18d1f816 update README to reflect dir name change 2018-06-07 01:08:41 -07:00
Andrew Zhu
c099e3173b rename /rpg-docs to /app 2018-06-07 01:07:49 -07:00
Andrew Zhu
de93636c7c Revert "add mixmax:smart-disconnect to reduce server load"
This reverts commit 465a61f80f.
2018-06-07 01:03:16 -07:00
Stefan Zermatten
5e263443b3 Updated dependencies 2018-06-04 15:42:23 +02:00
Stefan Zermatten
8c3a891254 Merge branch 'bugfix-163' 2018-06-04 15:29:14 +02:00
Stefan Zermatten
e737067990 Made sure empty party names take up some space so they can be edited 2018-06-04 15:28:08 +02:00
Andrew Zhu
465a61f80f add mixmax:smart-disconnect to reduce server load 2018-05-27 22:58:02 -07:00
Andrew Zhu
bbf42aaf97 make ratelimiter actually match, return time to reset on hitting ratelimit 2018-05-25 01:39:35 -07:00
Andrew Zhu
b20d086a24 add character JSON endpoint, improve ratelimit rules 2018-05-25 01:34:39 -07:00
Stefan Zermatten
52baf297ca Changed rpg-docs folder to app 2018-05-21 14:21:07 +02:00
Thaum Rystra
45e9f491ff Merge branch 'feature-blacklist' 2018-04-02 14:27:24 +02:00
Thaum Rystra
742b26b0de Added rate limiting logging and an error page for hitting the rate limit on opening characters 2018-04-02 14:26:55 +02:00
Stefan Zermatten
164ba78c81 Added blacklist checks and rate limit logging
Needs testing
2018-03-12 09:22:04 +02:00
Thaum Rystra
e27211b24d Merge branch 'enhancement-154' 2018-03-03 20:13:03 +02:00
Thaum Rystra
30987752cc Hotfix - Remove localhost from image link on printed character sheet 2018-03-03 17:28:42 +02:00
Thaum Rystra
058ee2691f Merge branch 'feature-print'
# Conflicts:
#	rpg-docs/package-lock.json
2018-03-03 17:18:16 +02:00
Thaum Rystra
f0cf7f4956 Added QR code and finished page 1 2018-03-03 17:13:36 +02:00
Thaum Rystra
75c8720b04 Moved printed character sheets to their own page
This makes sure the entire printed sheet is rendered before the browser  attempts to print it, solving all manner of errors
2018-03-03 11:13:16 +02:00
Thaum Rystra
f73f2f670f Formatted all existing printed character sheet fields nicely 2018-03-02 21:40:21 +02:00
Thaum Rystra
c6e62e1cfa Added borders to ability scores and AC 2018-03-02 07:25:37 +02:00
Jacob
4e574c0f51 Added "clear" (reset to 0) button to resource cards 2018-02-24 14:49:42 +00:00
Jacob
80b195b7f7 Added reset button to resource cards 2018-02-24 14:42:41 +00:00
Thaum Rystra
67956d9a42 Fixed dropdown boxes being clipped in dialogs, updated Meteor 2018-01-26 18:18:49 +02:00
Stefan Zermatten
64b3ca6066 Added proficiencies to printed sheet; some fixes 2017-12-12 15:54:21 +02:00
Stefan Zermatten
8349f7da9b Merge branch 'master' into feature-print 2017-12-06 09:22:57 +02:00
Stefan Zermatten
0636042878 Added core-js because an error message told me to 2017-11-22 14:34:54 +02:00
Stefan Zermatten
6207ffa516 Updated meteor 2017-11-21 10:53:07 +02:00
Stefan Zermatten
9d33612054 Merge branch 'feature-rate-limiting-api'
closes #141
2017-11-21 08:50:03 +02:00
Stefan Zermatten
face6387a0 Fixed attacks not being added to spells added with multi-add
Fixes #145
2017-11-01 15:55:07 +02:00
Stefan Zermatten
b308595dac Implement both tokens and rate limiting to API
Closes #141, but still needs better UI on failure
2017-10-12 16:24:12 +02:00
Stefan Zermatten
1d2de197a4 Replaced trello with github on home page 2017-09-28 14:04:42 +02:00
Stefan Zermatten
a3d790b47d Merge branch 'feature-manage-user' 2017-09-28 13:47:46 +02:00
Stefan Zermatten
efe6dd87db Added link to change password
closes #78
2017-09-28 13:47:30 +02:00
Stefan Zermatten
5b33a6e783 Fixed username dialog title 2017-09-28 13:22:21 +02:00
Stefan Zermatten
8730fab40b Moved username dialog to own folder 2017-09-28 13:21:14 +02:00
Stefan Zermatten
992776bb40 Merge branch 'feature-onboarding' 2017-09-28 13:18:52 +02:00
Stefan Zermatten
bc9ec4421c Polished onboarding, removed stray logs
closes #102
2017-09-28 13:17:33 +02:00
Stefan Zermatten
4c31ab601c Improved new user experience and fixed errors on character delete 2017-09-28 13:03:54 +02:00
Stefan Zermatten
c4e77c7eae Replaced all paper-tooltip with custom, working version 2017-09-28 11:47:03 +02:00
Stefan Zermatten
2cd6e27f70 Finished basic new user experience 2017-09-28 10:26:45 +02:00
Stefan Zermatten
f6b2dde479 Added basic onboarding steps 2017-09-27 16:19:00 +02:00
Stefan Zermatten
44da62a962 Lowered subscription caching to improve performance 2017-09-26 15:11:03 +02:00
Stefan Zermatten
4e96047e90 Added rate limiting to heavy subscriptions 2017-09-26 14:59:41 +02:00
Stefan Zermatten
212986ac37 Fixed missing charId 2017-09-26 13:54:33 +02:00
Stefan Zermatten
877f516565 Added efficient computation of characters to replace heavy export functionality 2017-09-26 13:36:01 +02:00
Thaum Rystra
750022f0f1 Added missing indices 2017-09-24 04:02:15 +02:00
Stefan Zermatten
614284c73d Updated Meteor and some packages 2017-09-22 12:52:21 +02:00
Stefan Zermatten
6528fc8bab Improved vMix export
closes #138
2017-09-22 11:41:32 +02:00
Stefan Zermatten
020930b2e4 Checked if old hitpoint slider exists before resetting it
closes #135
2017-09-22 11:03:00 +02:00
Stefan Zermatten
dcd76e06e1 Merge branch 'fixbug-137-multiadd-spells-delete-themselves' 2017-09-22 10:43:04 +02:00
Stefan Zermatten
8a58002415 Fixed spells in the library using their library ID's in the spells collection
closes #137
2017-09-22 10:42:34 +02:00
Stefan Zermatten
535fcd77cf Added missing attributes to vmix export 2017-09-13 16:01:05 +02:00
Stefan Zermatten
7c2aed26a4 Fixed vMix export, included vMix parties 2017-09-13 14:01:07 +02:00
Stefan Zermatten
fab052050a Merge branch 'feature-vmix' 2017-09-13 10:01:46 +02:00
Stefan Zermatten
b7bdb141c8 Added basic vMix server side api 2017-09-13 10:01:32 +02:00
Stefan Zermatten
00a050d337 Added basic printing functionality 2017-09-11 09:27:01 +02:00
Stefan Zermatten
0b8fabde14 Merge branch 'enhancement-106' 2017-09-08 10:48:44 +02:00
Stefan Zermatten
3336e177d9 Merge branch 'enhancement-spell-library-style'
closes #106
2017-09-08 10:46:41 +02:00
Stefan Zermatten
2e9440e325 Merge branch 'enhancement-spell-library-style' 2017-09-08 09:47:53 +02:00
Stefan Zermatten
e4ac400cbd Replaced spell library spell selection highlighting with checkboxes
Makes it more intuitive that multiple items can be selected
2017-09-08 09:47:43 +02:00
Stefan Zermatten
f600999c5f Guarded against error when hiding feature description portion of card 2017-09-08 09:46:58 +02:00
Stefan Zermatten
0af905699a Uncommented buffs from spell library 2017-09-08 09:46:00 +02:00
Stefan Zermatten
6e900cfaae Merge pull request #123 from Dumbgenius/spells-improvements
Spells improvements
2017-09-08 08:30:56 +02:00
Stefan Zermatten
1f42d3c622 Merge pull request #128 from Dumbgenius/fix-127
[#127] Arrow library items should give 20 arrows rather than one "Arrows (20)"
2017-09-08 08:29:56 +02:00
Stefan Zermatten
ee453d968f Merge pull request #134 from Dumbgenius/fixbug-attackview-damage-type
attackView now correctly shows damage type
2017-09-07 16:31:26 +02:00
Stefan Zermatten
0850e59b30 Merge pull request #122 from Dumbgenius/feature-22-temp-hp, requires migration
Feature - TempHP as a character attribute,
2017-09-07 16:29:55 +02:00
Stefan Zermatten
f3e44cf033 Moved polymer styling to app-theme 2017-09-07 16:26:09 +02:00
Stefan Zermatten
907f9d15d4 Merge pull request #131 from Dumbgenius/feature-custom-buffs
Feature - custom buffs
2017-09-07 14:42:05 +02:00
Stefan Zermatten
4c3d5d40dd Merge branch 'master' into feature-custom-buffs 2017-09-07 14:41:33 +02:00
Stefan Zermatten
ef0deb20aa Swapped dropdown box for full character list 2017-09-07 14:36:45 +02:00
Stefan Zermatten
b43ee08d75 Fixed some editing bugs on new buffs, improved style 2017-09-07 14:33:59 +02:00
Stefan Zermatten
7e7f1ec997 Merge pull request #132 from Dumbgenius/enhancement-blank-descriptions
Removed blank bit of card on features with no short description
2017-09-07 12:03:32 +02:00
Stefan Zermatten
0f652f5c74 Merge pull request #126 from Dumbgenius/feature-unsharing-characters
Added ability to "unshare" a character.
2017-09-07 12:01:14 +02:00
Stefan Zermatten
3e02875eaf Merge pull request #130 from Dumbgenius/enhancement-85
Added "carried" checkbox to container edit screen (resolves #85)
2017-09-07 11:54:11 +02:00
Stefan Zermatten
b9a5230344 Merge pull request #129 from Dumbgenius/enhancement-84
Added attacks to features (resolves #84)
2017-09-07 11:49:03 +02:00
Stefan Zermatten
28780b96c3 Merge pull request #120 from Dumbgenius/feature-drag-drop-spells
Feature - drag and drop spells
2017-09-07 11:43:34 +02:00
Jacob
bec0b33805 attackView now correctly shows damage type 2017-09-06 18:45:43 +01:00
Jacob
ad9ccbe7ef Removed blank bit of card on features with no short description 2017-09-05 14:31:26 +01:00
Jacob
e2933c2df5 Fixed a couple of places where "Buff" should've been "Condition" 2017-09-05 13:56:15 +01:00
Jacob
87583fdac6 Added buff details to apply buff dialog 2017-09-05 13:48:26 +01:00
Jacob
68e1382aed Buffs/conditions now have a delete button on the list on Stats 2017-09-04 20:36:13 +01:00
Jacob
7b62c82e32 Buff and condition view dialogs now only have the delete button 2017-09-04 20:16:56 +01:00
Jacob
6dd92586a4 Conditions are now separate from buffs, like in #109 2017-09-04 20:16:38 +01:00
Jacob
b3d0db1f02 Merge branch 'master' into feature-custom-buffs 2017-09-04 18:22:04 +01:00
Jacob
5f35c71c9d Added "carried" checkbox to container edit screen
Resolves #85
2017-09-04 15:50:57 +01:00
Jacob
85baf4e5d1 Added attacks to features (resolves #84) 2017-09-04 15:35:52 +01:00
Jacob
15d797131e Fixes #127. Also fixes it for other ammo as well as iron spikes 2017-09-04 15:22:45 +01:00
Stefan Zermatten
06ac9f70c8 Merge pull request #119 from Dumbgenius/srd-fixes
SRD fixes,

Merging this pull request doesn't change the deployed version of DiceCloud, but indicates that the database has been updated using the affected JSON files
2017-09-04 10:50:42 +02:00
Jacob
471a3e274e Refactored code which applies buffs 2017-08-10 02:49:29 +01:00
Jacob
d4031dc4a7 Removed an unnecessary console log 2017-08-09 16:11:09 +01:00
Jacob
18e5ab3f21 Can now set target of buffs
...to "Self only", "Others only", or "Both".
2017-08-09 16:08:19 +01:00
Jacob
c9fe2f17a0 If buff's target is "self", dialog is no longer shown 2017-08-09 15:48:12 +01:00
Jacob
818cb3905f Can now access the name of public characters for buffs
the buffDialog can now access the name of a publicly-available character
so it can display their name as the caster
2017-08-09 15:30:33 +01:00
Jacob
64ef426035 Applying character no longer duplicated in buff apply list 2017-08-09 15:30:25 +01:00
Jacob
2b188f1a8d Merge branch 'master' into feature-custom-buffs
# Conflicts:
#	rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html
#	rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
2017-08-09 15:03:54 +01:00
Jacob
08735ea4f7 Added ability to add/edit buffs 2017-08-09 15:01:20 +01:00
Jacob
bce1b85600 Relocated the buff list to be inline in stats.html 2017-08-09 11:39:18 +01:00
Jacob
0d023e2ba3 Adding conditions now works again 2017-08-09 11:36:45 +01:00
Jacob
dad575de64 Buffs are now split into custom buffs and conditions 2017-08-09 11:20:49 +01:00
Jacob
cb648b4a28 Buffs now display who and what they were applied by. 2017-08-09 11:13:24 +01:00
Jacob
1279137362 Buffs now display attacks and proficiencies in buffDialog 2017-08-09 10:42:34 +01:00
Jacob
3c06529906 buffViewList is now split into "Conditions" and "Buffs" 2017-08-09 10:16:50 +01:00
Jacob
a3b0c6cafd Added a schema/collection pair for CustomBuffs 2017-08-09 10:06:11 +01:00
Jacob
a1d9f7f5bb Added ability to remove self from readers.
Closes #125.
2017-08-09 02:18:46 +01:00
Jacob
3b03e9c71c Spell library now allows you to select multiple spells 2017-07-30 23:27:29 +01:00
Jacob
0eea6f2386 Cantrips now display as "<school> cantrip" rather than "Level 0 <school>"
Also fixed spacing if "(ritual)" tag was not present
2017-07-30 21:20:27 +01:00
Jacob
8f8714d3e5 Altered spacing of headers within baseDialog 2017-07-30 21:14:02 +01:00
Jacob
7c38e8d70a Added newline before At Higher Levels on Healing Word. 2017-07-30 20:22:59 +01:00
Jacob
826859ca3f Added migration to add TempHP to all characters 2017-07-29 22:41:37 +01:00
Jacob
2ca13fbb56 Renamed tempHitPointSlider to extraHitPointSlider in the css 2017-07-29 21:45:02 +01:00
Jacob
6d801e0178 Renamed old "Temporary HP" to "Extra HP" (THP is now the actual attribute) 2017-07-29 21:42:48 +01:00
Jacob
9ddac7d5cd Temporary HP (the attribute) now appears on Stats page 2017-07-29 21:25:05 +01:00
Jacob
7a6f751e30 THP added as a character attribute 2017-07-29 20:09:48 +01:00
Jacob
2389768234 statOrder is now set programatically from an array
This makes it far easier to add new stats, as we don't have to update
every single number manually.
2017-07-29 19:20:26 +01:00
Jacob
c76fe99148 Can now move and copy spells between characters 2017-07-26 21:02:36 +01:00
Jacob
53afaa4f37 CTRL-dragging now copies spells 2017-07-26 19:54:18 +01:00
Jacob
3599b5fbc4 Added basic drag-and-drop functionality between spell lists
Closes #105
2017-07-26 19:23:41 +01:00
Jacob
275fb1ca65 Scrying's tables are now actual Markdown tables 2017-07-21 15:46:33 +01:00
Jacob
d5d937b04a Reincarnate's race table is now an actual Markdown table 2017-07-21 15:38:17 +01:00
Jacob
aa554adbce Teleport's familiarity table is now a table 2017-07-21 15:30:39 +01:00
Jacob
cb739eb207 Removed indents after double-newlines in spells SRD.
They don't actually render in Markdown, and if there was an indent of >=4
spaces it caused the entire next line to be rendered as code (for example
in the Animate Objects spell).
2017-07-21 14:58:29 +01:00
Jacob
66df2ea4aa Animate Objects' stats are now in a table 2017-07-21 14:43:37 +01:00
Stefan Zermatten
73d1419ee9 Merge pull request #114 from Dumbgenius/misc-enhancements
Miscellaneous enhancements
2017-07-21 13:12:22 +02:00
Stefan Zermatten
681ef614c7 Move earth now level 6 2017-07-21 13:03:31 +02:00
Stefan Zermatten
2bdbcb2e79 Fixed speak with animals
#118
2017-07-21 13:02:50 +02:00
Stefan Zermatten
c119fcfbb8 Freezing sphere now level 6
#118
2017-07-21 13:00:28 +02:00
Stefan Zermatten
deb5db8657 Daylight now level 3 2017-07-21 12:59:54 +02:00
Stefan Zermatten
49522580e3 Replaced line breaks with double line breaks in SRD spells 2017-07-21 12:50:50 +02:00
Stefan Zermatten
5b50f20128 Fixed spell attacks from library assignment of default attack bonus 2017-07-21 12:49:04 +02:00
Stefan Zermatten
52fa97c952 removed default values for library attacks where they aren't needed 2017-07-21 12:48:03 +02:00
Stefan Zermatten
e7a5ce8241 Replaced all mentions of saving throws with DC {DC} saving throw 2017-07-21 12:02:17 +02:00
Stefan Zermatten
d1b9043e1f Added change procedure for updating from srd JSON 2017-07-21 11:53:42 +02:00
Stefan Zermatten
9ffc5649f7 Fixed tooltip suffix icons and moved textarea icons outside of textarea 2017-07-21 11:02:02 +02:00
Stefan Zermatten
15e6c12c03 Replaced shitty paper-tooltip with custom css tooltip 2017-07-21 11:01:18 +02:00
Jacob
e89b877326 Merge branch 'fixbug-116' into misc-enhancements 2017-07-20 16:18:49 +01:00
Jacob
aff2f1f438 Parties are now sorted in both character lists. 2017-07-20 16:18:15 +01:00
Jacob
71d1e9e9e8 Added links in the guide to adam-p's Markdown Cheatsheet
and to the original specifaction for Markdown.
2017-07-20 12:21:01 +01:00
Jacob
0696fd8447 Updated the guide to talk about Markdown support as well 2017-07-20 12:10:17 +01:00
Jacob
b2db33e0f3 Added attacks to SRD cantrips
they scale by level: {floor((Level+1)/6)+1}dX
2017-07-20 11:33:44 +01:00
Jacob
0c2842b84a All "At Higher Levels" are now bold and italic, like in the actual PHB 2017-07-19 12:34:01 +01:00
Jacob
789658cfe7 Spells imported from SRD now have attack bonus set by default 2017-07-19 12:31:50 +01:00
Jacob
3be3da777f Now we can use "attackBonus" in spell attacks, make that the default 2017-07-19 12:19:26 +01:00
Jacob
0e53f157d2 Spell attack bonus and DC are now available in spell attacks. 2017-07-19 12:13:49 +01:00
Jacob
24cc4fd2b1 Background proficiencies now correctly open background dialog
Previously, when clicking on non-skill proficiencies from a character's
background, it would open a blank rectangle as its parent dialog. Now, it
correctly opens the Background dialog.
2017-07-18 23:01:14 +01:00
Jacob
1f0ea689dc Multiple instances of the same proficiency are now merged
For tool/language/weapon/armour proficiencies, on the stats/persona page.
2017-07-18 22:33:28 +01:00
Jacob
11adf9da04 Merge branch 'fixbug-107' into misc-enhancements
# Conflicts:
#	rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
2017-07-18 22:05:31 +01:00
Jacob
5ca81056f9 Merge branch 'feature-swap-stat-modifier' into misc-enhancements 2017-07-18 21:58:29 +01:00
Jacob
6fc469f934 Added setting to swap stats and modifiers
Although Schemas.Character was changed, a database migration should
not be required due to the way characterSettings handles it (since
undefined is a falsy value).
2017-07-18 21:58:00 +01:00
Jacob
0c7948afdd Changed all "1 action" casting times to "action" so it displays properly
i.e. it displays as "Evocation action" rather than "Evocation 1 action"
for example

Also did the same for "1 bonus action" -> "bonus action"
2017-07-18 20:24:12 +01:00
Jacob
42ffc79499 Spell list now displays whether a spell requires GP. 2017-07-18 20:22:44 +01:00
Jacob
0240209410 Removed buffViewList that shouldn't have been there 2017-07-18 20:20:04 +01:00
Jacob
c4c1afa669 Merge branch 'guide-update-1.2.8' into misc-enhancements
# Conflicts:
#	rpg-docs/client/views/guide/guide.css
#	rpg-docs/client/views/guide/guide.html
2017-07-18 19:58:35 +01:00
Jacob
47ac090e9d Merge branch 'testing_dg' into misc-enhancements
# Conflicts:
#	rpg-docs/.meteor/packages
2017-07-18 19:56:26 +01:00
Jacob
aadc83391f Adding an attack to a spell now has attack bonus set by the spell list
Default damage is also changed to 1d10 fire for spells
2017-07-18 19:52:12 +01:00
Jacob
54fb398056 Merge branch 'master' into guide-update-1.2.8 2017-07-18 11:11:37 +01:00
Jacob
073094f6dd Merge branch 'master' into fixbug-107 2017-07-18 11:11:14 +01:00
Jacob
bb95fe0d9a Merge branch 'master' into feature-conditions 2017-07-18 11:10:04 +01:00
Jacob
832ed0c1ff A sort of hacky fix to the issue - text wrapping doesn't quite work 2017-07-17 04:43:39 +01:00
Jacob
17a390a8aa Changed Model of Buffs for forwards compatibility
Added a parent attribute and made Buffs a child, so that buffs can be
added to items, spells and features.
2017-07-17 02:41:51 +01:00
Jacob
e65a2db0d2 Changed guide to use Markdown rather than HTML formatting (mostly)
This is both for ease of editing and so that that it can be copied
into the GitHub wiki.

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

Closes #15
2015-10-03 22:40:04 +02:00
Stefan Zermatten
d05874ed13 Bumped version 2015-10-01 08:06:29 +02:00
Stefan Zermatten
df2521e69c Merge pull request #5 from ThaumRystra/bugfix-unprepared-spell-attacks
Spell child attacks now set their enabled state to match the parent spell prepared state
2015-10-01 08:03:30 +02:00
Stefan Zermatten
3c6a685fe8 Fixed reference to parentId which should be parent.id 2015-10-01 07:45:38 +02:00
Connor Petersen
a77e560284 Spell attacks correctly enable based on preparedness, Model correction with collection hooks 2015-09-30 13:30:59 -07:00
Stefan Zermatten
4cec83918f Updated README.md to remove out of date info 2015-09-28 07:35:10 +02:00
Stefan Zermatten
fec95c51c6 Rolled back changes to spells tab, It wasn't a column-layout 2015-09-25 13:03:14 +02:00
Stefan Zermatten
425c42d049 Bumped version 2015-09-25 12:52:28 +02:00
Stefan Zermatten
ab6f0c4f5b Merge branch 'fix-columns' 2015-09-25 12:51:04 +02:00
Stefan Zermatten
5d6e57b896 Wrap cards in padded divs to make columns behave 2015-09-25 12:49:48 +02:00
Stefan Zermatten
7c0a8125f2 Merge branch 'fix-carry-capacity-effectView' 2015-09-04 13:56:06 +02:00
Stefan Zermatten
7481ef08a8 Carry capacity effects no longer show up as "no stat" when viewed 2015-09-04 13:55:42 +02:00
Stefan Zermatten
b578dd5fb0 Merge branch 'feature-carry-capacity-modifier' 2015-09-03 14:09:51 +02:00
Stefan Zermatten
5d6f934d88 Bumped version 2015-09-03 14:09:23 +02:00
Stefan Zermatten
337f0bfa8a Made sure migration touches every character 2015-09-03 14:09:17 +02:00
Stefan Zermatten
c62784894b Made sure encumbered conditions respect carry capacity 2015-09-03 13:53:00 +02:00
Stefan Zermatten
75fff43d7d Gave an effect menu option for carry capacity 2015-09-03 13:52:40 +02:00
Stefan Zermatten
a9eeeac0df Fixed carry capacity bar 2015-09-03 13:52:22 +02:00
Stefan Zermatten
c8af0ff0a9 Fixed carry capacity table 2015-09-03 13:52:06 +02:00
Stefan Zermatten
9e200db7b9 Made carry capacity an attribute, migrations need testing 2015-08-31 15:51:52 +02:00
Stefan Zermatten
c08cf83096 Bumped version 2015-08-27 12:21:23 +02:00
Stefan Zermatten
d9368b06d0 Merge branch 'bugfix-0.6.8' 2015-08-27 12:07:42 +02:00
Stefan Zermatten
2703367681 Proficiencies now get disabled when their features are disabled 2015-08-27 12:05:56 +02:00
Stefan Zermatten
d419442549 Fixed share dialog not finding usernames or email addresses 2015-08-27 11:59:38 +02:00
Stefan Zermatten
99df01c950 Prevented negative temporary hitpoints being added 2015-08-27 11:43:34 +02:00
Stefan Zermatten
d76349b3bb Merge branch 'release-0.6.7a' 2015-07-29 09:57:06 +02:00
Stefan Zermatten
39c061f4e8 Fixed formatting of not found page, now has a toolbar and centered text 2015-07-29 09:55:22 +02:00
Stefan Zermatten
6d167ddb22 Fixed formatting of loading page, more padding 2015-07-29 09:55:01 +02:00
Stefan Zermatten
037acbd459 Added Starter Set Wizard to the front page 2015-07-29 09:54:43 +02:00
Stefan Zermatten
4d3fc3bb09 Fixed accidental back() requests when closing detail dialogs 2015-07-29 09:54:18 +02:00
Stefan Zermatten
4b984d4fac Made analytics show all characters as the same page 2015-07-29 09:53:46 +02:00
Stefan Zermatten
58843613ba Merge branch 'release-0.6.7' 2015-07-27 13:12:42 +02:00
Stefan Zermatten
39b549b24b Bumped version 2015-07-27 13:12:28 +02:00
Stefan Zermatten
c79177de72 Added Google Analytics 2015-07-27 13:10:33 +02:00
Stefan Zermatten
11d09b1487 Fixed effect values not being visible on mobile 2015-07-27 12:16:02 +02:00
Stefan Zermatten
0e4918d57d Merge branch 'hotfix-style' 2015-07-27 12:08:22 +02:00
Stefan Zermatten
949f313af2 removed 1st column of all tables being 100px 2015-07-27 12:08:15 +02:00
Stefan Zermatten
85b63f152f Merge branch 'release-0.6.6' into develop 2015-07-27 11:23:12 +02:00
Stefan Zermatten
2141d52a7a Merge branch 'release-0.6.6' 2015-07-27 11:22:41 +02:00
Stefan Zermatten
4c84235064 Bumped version 2015-07-27 11:22:26 +02:00
Stefan Zermatten
0e194a5408 Added markdown to text areas 2015-07-27 10:21:26 +02:00
Stefan Zermatten
4b60eac330 Fixed typo on change log 2015-07-24 11:52:46 +02:00
Stefan Zermatten
cb017c359d Merge branch 'release-0.6.5' into develop 2015-07-24 11:31:05 +02:00
Stefan Zermatten
15aaaa5c14 Merge branch 'release-0.6.5' 2015-07-24 11:29:50 +02:00
Stefan Zermatten
290bee83b4 Bumped version 2015-07-24 11:29:03 +02:00
Stefan Zermatten
fcd2461205 Merge branch 'master' into release-0.6.5 2015-07-24 11:26:46 +02:00
Stefan Zermatten
52198d0249 Disabled edit buttons for read-only viewers 2015-07-23 15:07:39 +02:00
Stefan Zermatten
ba7ccfdfa0 Added support for character profile pictures 2015-07-23 15:04:43 +02:00
Stefan Zermatten
92d3b086fa net worth calculations now take into account your containers' values 2015-07-23 09:14:55 +02:00
Stefan Zermatten
e180595dcd Merge branch 'release-0.6.4' 2015-07-06 13:37:02 +02:00
Stefan Zermatten
ed708bdde0 bumped version 2015-07-06 13:36:50 +02:00
Stefan Zermatten
d7852d640f Fixed Math using min and max incorrectly in skills 2015-07-03 13:21:26 +02:00
Stefan Zermatten
a2fdee88b6 Ordered Character lists 2015-06-29 14:00:55 +02:00
Stefan Zermatten
af070b1578 Attacks inherit spell names correctly 2015-06-29 13:55:27 +02:00
Stefan Zermatten
83a8eeef0f Fixed editing of multiple attacks 2015-06-29 13:55:15 +02:00
Stefan Zermatten
34f8e7402b Hit dice constitution now has a + sign when positive 2015-06-29 10:21:49 +02:00
Stefan Zermatten
1b7e2cd850 Merge branch 'hotfix-rounding' into develop 2015-06-29 10:04:45 +02:00
Stefan Zermatten
463b7f0fc9 Merge branch 'hotfix-rounding' 2015-06-29 09:45:38 +02:00
Stefan Zermatten
266495abc8 Bumped version 2015-06-29 09:44:57 +02:00
Stefan Zermatten
453d4365d3 Prevent memoized values being used in dependency loops 2015-06-29 09:41:01 +02:00
Stefan Zermatten
e0ce6275bf Floor character attribute values 2015-06-29 09:40:37 +02:00
Stefan Zermatten
16b16ce6c6 Merge branch 'hotfix-minmax' into develop 2015-06-26 10:49:10 +02:00
Stefan Zermatten
80c72a274e Merge branch 'hotfix-minmax' 2015-06-26 10:48:43 +02:00
Stefan Zermatten
91f0f7954c Bumped version 2015-06-26 10:48:33 +02:00
Stefan Zermatten
c74abcb608 Fixed min and max error 2015-06-26 10:48:04 +02:00
Stefan Zermatten
da8b91594e Removed reference to character helper 2015-06-25 14:22:07 +02:00
Stefan Zermatten
fc26f5a73e Merge branch 'hotfix-experience-inputs' into develop 2015-06-25 14:20:54 +02:00
Stefan Zermatten
4f60766d5d Merge branch 'hotfix-experience-edit-inputs' 2015-06-25 14:07:34 +02:00
Stefan Zermatten
e992aeebef Bumped version 2015-06-25 14:07:24 +02:00
Stefan Zermatten
4108346a98 Fixed experience inputs updating 2015-06-25 14:07:13 +02:00
Stefan Zermatten
946fadadc2 Merge branch 'release-0.6.0' 2015-06-25 13:49:25 +02:00
Stefan Zermatten
d2cc2833a9 Merge branch 'release-0.6.0' into develop 2015-06-25 13:48:32 +02:00
Stefan Zermatten
af57326194 Bumped version 2015-06-25 13:48:12 +02:00
Stefan Zermatten
98c69e9e17 Style tweaks 2015-06-25 13:38:33 +02:00
Stefan Zermatten
395edd0563 Character settings now formatted and has done button 2015-06-25 13:38:20 +02:00
Stefan Zermatten
43e87e7786 Container weight summaries now round off 2015-06-25 13:37:55 +02:00
Stefan Zermatten
ad347504c6 Fixed experienceDialog updating polymer inputs 2015-06-25 13:37:03 +02:00
Stefan Zermatten
4e6e99b695 Fixed spellDialog formatting 2015-06-25 13:36:42 +02:00
Stefan Zermatten
104624a322 Merge branch 'feature-soft-memoize' into develop 2015-06-25 13:35:19 +02:00
Stefan Zermatten
79d166e6af Removed references to old calculations 2015-06-25 13:34:44 +02:00
Stefan Zermatten
86c934e8ac Began replacing calls to helpers with calls to memoized functions 2015-06-18 15:14:37 +02:00
Stefan Zermatten
a034cbf30e Duplicate character helpers with memoized functions not attached to characters 2015-06-18 13:34:59 +02:00
Stefan Zermatten
d5680ebf8a Add memoize functionality 2015-06-18 13:33:54 +02:00
Stefan Zermatten
53f2fcc945 Merge branch 'release-0.5.7' into develop 2015-06-17 14:32:35 +02:00
Stefan Zermatten
612e127be4 Merge branch 'release-0.5.7' 2015-06-17 14:22:33 +02:00
Stefan Zermatten
2b0d975cee change logs 2015-06-17 14:22:12 +02:00
Stefan Zermatten
248ab9bb6b Added moving item dialog, that can't actually be triggered 2015-06-17 14:15:49 +02:00
Stefan Zermatten
314ce85410 Attacks can now be added to spells 2015-06-17 09:05:23 +02:00
Stefan Zermatten
9ff45dbcc2 backgrounds now have proficiencies 2015-06-17 08:49:26 +02:00
Stefan Zermatten
6caf19bc99 Merge branch 'release-0.5.6' into develop 2015-06-15 13:36:50 +02:00
Stefan Zermatten
c2b04d0977 Merge branch 'release-0.5.6' 2015-06-15 13:34:29 +02:00
Stefan Zermatten
9012c4a558 change logs 2015-06-15 13:34:10 +02:00
Stefan Zermatten
65a84937f2 Merge branch 'feature-share-publically' into develop 2015-06-15 13:30:42 +02:00
Stefan Zermatten
eebb88b6b1 New front page and darker style 2015-06-15 13:29:33 +02:00
Stefan Zermatten
06ab7c5116 Merge branch 'master' into feature-share-publically 2015-06-15 10:04:40 +02:00
Stefan Zermatten
89f03c7601 Merge branch 'hotfix-gmail-report' 2015-06-12 08:04:23 +02:00
Stefan Zermatten
9d2eb14c0c Change logs 2015-06-12 08:03:46 +02:00
Stefan Zermatten
7b3cb54983 Added gmail email address senders to the report emails 2015-06-12 08:02:33 +02:00
Stefan Zermatten
a09bad2fed Change logs 2015-06-10 11:13:58 +02:00
Stefan Zermatten
afd897edfe Merge branch 'Hotfix' 2015-06-10 11:05:01 +02:00
Stefan Zermatten
efc79cb6e7 Fixed net value calculation to avoid rounding errors 2015-06-10 11:00:42 +02:00
Stefan Zermatten
35efe39ea7 Made feedback reports send emails "from" their creator's address 2015-06-10 11:00:19 +02:00
Stefan Zermatten
034067bd6e Added link to example character 2015-06-10 10:58:47 +02:00
Stefan Zermatten
0d75cd5d15 Added sharing to anyone with link, changed home page 2015-06-09 17:06:51 +02:00
Stefan Zermatten
4f1376a666 Change log 2015-06-04 09:57:49 +02:00
Stefan Zermatten
78b1d71b9d Overhauled how effects are edited 2015-05-27 13:13:51 +02:00
Stefan Zermatten
1323d8006c Made feedback not sendable without title & description 2015-05-27 09:18:34 +02:00
Stefan Zermatten
87d722adaf Now send emails to myself when feedback gets reported 2015-05-27 08:33:24 +02:00
Stefan Zermatten
90e511eb00 Added the ability to hide the spells tab for a character 2015-05-27 08:10:14 +02:00
Stefan Zermatten
5b8c25f5de Fixed a harmless error with un-set effect views 2015-05-27 08:04:39 +02:00
Stefan Zermatten
2fbc54fee8 Edited the guide to be "open beta" 2015-05-25 09:23:38 +02:00
Stefan Zermatten
a064ae3fe8 Added Kadira 2015-05-25 08:33:14 +02:00
Stefan Zermatten
ba9b518d7e Added subsManager 2015-05-22 14:36:05 +02:00
Stefan Zermatten
2f729070b2 Updated change log 2015-05-22 14:24:22 +02:00
Stefan Zermatten
7aedb9451c Base values now don't look like added values 2015-05-22 14:17:38 +02:00
Stefan Zermatten
c6886dd49e Floaty menus now close when clicking on a sub-button 2015-05-22 14:14:20 +02:00
Stefan Zermatten
038ce490e4 Added subsmanager to stop characters getting forgotten between page changes 2015-05-22 14:11:22 +02:00
Stefan Zermatten
52bef57637 Added encumbrance effects, conditions and encumbrance buffs 2015-05-22 14:04:09 +02:00
Stefan Zermatten
29e9f8c8dc Added quality-of-life UI to determining a character's encumbrance 2015-05-20 16:14:01 +02:00
Stefan Zermatten
fea02811ff Added buffs and standard conditions, no UI yet though 2015-05-20 16:13:25 +02:00
Stefan Zermatten
73cee52fff Fixed a bug in combining multiple resistances/vulnerabilities 2015-05-20 16:11:59 +02:00
Stefan Zermatten
b58c006ed4 Updated change log 2015-05-19 11:45:38 +02:00
Stefan Zermatten
ef44f6c1a5 Fixed migration of attack data 2015-05-19 11:44:06 +02:00
Stefan Zermatten
f455cea43f Fixed maths for strength calculations rounding, rather than rounding down 2015-05-18 14:31:07 +02:00
Stefan Zermatten
e4083bc744 Updated the change log 2015-05-18 14:16:24 +02:00
Stefan Zermatten
baffafb62a Added table of calculated values to strength detail 2015-05-18 14:13:28 +02:00
Stefan Zermatten
4143929667 Resolve merge conflicts 2015-05-18 13:21:28 +02:00
Stefan Zermatten
18286d1b9c Merge remote-tracking branch 'origin/master'
Conflicts:
	rpg-docs/client/style/main.scss
2015-05-18 13:20:24 +02:00
Stefan Zermatten
9f51567162 Added migrations for new data structure 2015-05-18 13:18:37 +02:00
Stefan Zermatten
66d8a3bfbf Moved dice.js and renamed it 2015-05-18 13:18:27 +02:00
Stefan Zermatten
a9648c10cc Fixed line break formatting in dialogs 2015-05-18 13:18:11 +02:00
Stefan Zermatten
679292373c Added item increment buttons 2015-05-18 13:17:40 +02:00
Stefan Zermatten
ae416458b5 Made attacks less rigid, now using inline computations 2015-05-18 13:16:33 +02:00
Stefan Zermatten
0ff4a887ea Updated the change log with v0.3.1 2015-05-16 00:26:00 +02:00
Stefan Zermatten
955794b5c0 Re-implemented paper-fab menu 2015-05-16 00:20:49 +02:00
Stefan Zermatten
b0ac1dcc29 Split cards into their own scss 2015-05-16 00:20:31 +02:00
Stefan Zermatten
83150bc527 Updated the change log 2015-05-15 16:59:54 +02:00
Stefan Zermatten
061f1fd0a5 Rewrite all css to scss and refactor html 2015-05-15 16:55:05 +02:00
Stefan Zermatten
e40dd196e6 Migrated stats page to scss 2015-05-13 15:25:22 +02:00
Stefan Zermatten
5dbb59ef80 skill-row now scss 2015-05-13 13:53:53 +02:00
Stefan Zermatten
49e25d7304 Started implementing core styles 2015-05-13 13:23:44 +02:00
Stefan Zermatten
85df0257e2 Added scss 2015-05-13 13:23:18 +02:00
Stefan Zermatten
b88bb95928 Stopped the sidebar from appearing except on displays> 900px 2015-05-13 12:51:35 +02:00
Stefan Zermatten
2122e543d5 Ensured disabled effects don't show up in skill and attribute detail boxes 2015-05-13 08:46:28 +02:00
Stefan Zermatten
a71519aaa7 Updated Polymer 2015-05-13 08:46:04 +02:00
Stefan Zermatten
2404845d51 Styling and rounding fixes for detail boxes 2015-05-12 11:34:56 +02:00
Stefan Zermatten
bf032bcdf3 Now only show edit and add buttons to writers, not readers 2015-05-12 11:34:37 +02:00
Stefan Zermatten
ff8ae89722 Improved feedback form style 2015-05-12 10:36:15 +02:00
Stefan Zermatten
80ca7307ce Added change log 2015-05-12 10:10:15 +02:00
Stefan Zermatten
a539b0bc6c limited how much info gets published to users about themselves 2015-05-12 09:33:32 +02:00
Stefan Zermatten
c6b3cad9c8 Improved character side list style 2015-05-12 09:32:40 +02:00
Stefan Zermatten
95b7b66390 Added quick feedback form 2015-05-12 09:32:28 +02:00
740 changed files with 42249 additions and 11056 deletions

14
.codio
View File

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

View File

@@ -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": true,
"disallowNewlineBeforeBlockStatements": true,
"disallowKeywordsOnNewLine": ["else"],
"requireSpaceAfterKeywords": [
"if",
"else",
"for",
"while",
"do",
"switch",
"return",
"try",
"catch"
],
"requireSpaceBeforeBinaryOperators": [
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
"&=", "|=", "^=", "+=",
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
"|", "^", "&&", "||", "===", "==", ">=",
"<=", "<", ">", "!=", "!=="
],
"requireSpaceAfterBinaryOperators": true,
"requireSpacesInConditionalExpression": true,
"requireSpacesInForStatement": true,
"requireTrailingComma": {
"ignoreSingleValue": true,
"ignoreSingleLine": true
},
"requireLineFeedAtFileEnd": true,
"disallowSpacesInAnonymousFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"disallowSpacesInsideObjectBrackets": "all",
"disallowSpacesInsideArrayBrackets": "all",
"disallowSpacesInsideParentheses": true,
"disallowMultipleLineBreaks": true,
"disallowNewlineBeforeBlockStatements": true
}

View File

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

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

674
License.md Normal file
View File

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

116
README.md
View File

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

11
app/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,19 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
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

56
app/.meteor/packages Normal file
View File

@@ -0,0 +1,56 @@
# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
accounts-password@1.6.2
accounts-ui@1.3.1
random@1.2.0
dburles:collection-helpers
reactive-var@1.0.11
underscore@1.0.10
momentjs:moment
dburles:mongo-collection-instances
percolate:migrations
accounts-google@1.3.3
email@2.0.0
meteorhacks:subs-manager
chuangbo:marked
meteor-base@1.4.0
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.3.0
ejson@1.1.1
check@1.3.1
standard-minifier-js@2.6.0
shell-server@0.5.0
seba:minifiers-autoprefixer
templates:array
ecmascript@0.14.3
es5-shim@4.8.0
reactive-dict@1.3.0
percolate:synced-cron
ongoworks:speakingurl
service-configuration@1.0.11
google-config-ui@1.0.1
dynamic-import@0.5.2
ddp-rate-limiter@1.0.9
rate-limit@1.0.9
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
peerlibrary:reactive-publish
simple:rest
simple:rest-method-mixin
mikowals:batch-insert
peerlibrary:subscription-data

1
app/.meteor/release Normal file
View File

@@ -0,0 +1 @@
METEOR@1.11.1

142
app/.meteor/versions Normal file
View File

@@ -0,0 +1,142 @@
accounts-base@1.7.0
accounts-google@1.3.3
accounts-oauth@1.2.0
accounts-password@1.6.2
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.2.1
aldeed:schema-index@3.0.0
allow-deny@1.1.0
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.7.1
bozhao:link-accounts@2.2.1
caching-compiler@1.2.2
caching-html-compiler@1.1.3
callback-hook@1.3.0
check@1.3.1
chuangbo:marked@0.3.5_1
coffeescript@2.4.1
coffeescript-compiler@2.4.1
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.9
ddp-server@2.3.2
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.5.3
ecmascript@0.14.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.11.0
ecmascript-runtime-server@0.10.0
ejson@1.1.1
email@2.0.0
es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
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.2
id-map@1.1.0
inter-process-messaging@0.1.1
jquery@1.11.11
lai:collection-extensions@0.2.1_1
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
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
meteortesting:browser-tests@1.3.4
meteortesting:mocha@1.1.5
meteortesting:mocha-core@7.0.1
mikowals:batch-insert@1.2.0
minifier-css@1.5.3
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.29.1
mongo@1.10.0
mongo-decimal@0.1.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
npm-bcrypt@0.9.3
npm-mongo@3.8.1
oauth@1.3.2
oauth2@1.3.0
observe-sequence@1.0.16
ongoworks:speakingurl@9.0.0
ordered-dict@1.1.0
patreon-oauth@0.1.0
peerlibrary:assert@0.3.0
peerlibrary:check-extension@0.7.0
peerlibrary:computed-field@0.10.0
peerlibrary:data-lookup@0.3.0
peerlibrary:extend-publish@0.6.0
peerlibrary:fiber-utils@0.10.0
peerlibrary:reactive-mongo@0.4.0
peerlibrary:reactive-publish@0.10.0
peerlibrary:server-autorun@0.8.0
peerlibrary:subscription-data@0.8.0
percolate:migrations@0.9.8
percolate:synced-cron@1.3.2
promise@0.11.2
raix:eventemitter@0.1.3
random@1.2.0
rate-limit@1.0.9
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.3.0
retry@1.1.0
routepolicy@1.1.0
seba:minifiers-autoprefixer@1.2.1
service-configuration@1.0.11
session@1.2.0
sha@1.0.9
shell-server@0.5.0
simple:json-routes@2.1.0
simple:rest@1.1.1
simple:rest-method-mixin@1.0.1
socket-stream-client@0.3.1
spacebars@1.0.15
spacebars-compiler@1.1.3
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
underscore@1.0.10
url@1.3.1
webapp@1.9.1
webapp-hashing@1.0.9

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">
<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'>
<script src="/components/webcomponentsjs/webcomponents.js"></script>
<meta name="apple-mobile-web-app-capable" content="yes">
<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">
@@ -23,4 +22,17 @@
<meta name="msapplication-TileColor" content="#b91d1d">
<meta name="msapplication-TileImage" content="/mstile-144x144.png?v=lk6WXp6Pmj">
<meta name="theme-color" content="#d12929">
<style type="text/css" media="print">
@page {
margin: 0mm;
}
html {
margin: 0px;
}
* {
-webkit-transition: none !important;
transition: none !important;
}
</style>
</head>

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

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

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/client/config.js';

0
app/grammar.js Normal file
View File

View File

@@ -0,0 +1,251 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import deathSaveSchema from '/imports/api/properties/subSchemas/DeathSavesSchema.js'
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import {assertEditPermission} from '/imports/api/sharing/sharingPermissions.js';
import { assertUserHasPaidBenefits } from '/imports/api/users/patreon/tiers.js';
import '/imports/api/creature/removeCreature.js';
import '/imports/api/creature/restCreature.js';
//set up the collection for creatures
let Creatures = new Mongo.Collection('creatures');
let CreatureSettingsSchema = new SimpleSchema({
//slowed down by carrying too much?
useVariantEncumbrance: {
type: Boolean,
optional: true,
},
//hide spellcasting tab
hideSpellcasting: {
type: Boolean,
optional: true,
},
// Swap around the modifier and stat
swapStatAndModifier: {
type: Boolean,
optional: true,
},
// Hide all the unused stats
hideUnusedStats: {
type: Boolean,
optional: true,
},
// How much each hitDice resets on a long rest
hitDiceResetMultiplier: {
type: Number,
optional: true,
min: 0,
max: 1,
},
discordWebhook: {
type: String,
optional: true,
max: 200,
},
});
let CreatureSchema = new SimpleSchema({
// Strings
name: {
type: String,
defaultValue: '',
optional: true,
},
alignment: {
type: String,
optional: true
},
gender: {
type: String,
optional: true
},
picture: {
type: String,
optional: true
},
avatarPicture: {
type: String,
optional: true,
},
// Mechanics
deathSave: {
type: deathSaveSchema,
defaultValue: {},
},
// Stats that are computed and denormalised outside of recomputation
denormalizedStats: {
type: Object,
defaultValue: {},
},
// Sum of all XP gained by this character
'denormalizedStats.xp': {
type: SimpleSchema.Integer,
defaultValue: 0,
},
// Sum of all levels granted by milestone XP
'denormalizedStats.milestoneLevels': {
type: SimpleSchema.Integer,
defaultValue: 0,
},
// Inventory
'denormalizedStats.weightTotal': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.weightEquipment': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.weightCarried': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.valueTotal': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.valueEquipment': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.valueCarried': {
type: Number,
defaultValue: 0,
},
'denormalizedStats.itemsAttuned': {
type: Number,
defaultValue: 0,
},
// Version of computation engine that was last used to compute this creature
computeVersion: {
type: String,
optional: true,
},
type: {
type: String,
defaultValue: 'pc',
allowedValues: ['pc', 'npc', 'monster'],
},
damageMultipliers: {
type: Object,
blackbox: true,
defaultValue: {}
},
variables: {
type: Object,
blackbox: true,
defaultValue: {}
},
// Tabletop
tabletop: {
type: String,
regEx: SimpleSchema.RegEx.id,
optional: true,
},
initiativeRoll: {
type: SimpleSchema.Integer,
optional: true,
},
// Settings
settings: {
type: CreatureSettingsSchema,
defaultValue: {},
},
});
CreatureSchema.extend(ColorSchema);
CreatureSchema.extend(SharingSchema);
Creatures.attachSchema(CreatureSchema);
const insertCreature = new ValidatedMethod({
name: 'creatures.insertCreature',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run() {
if (!this.userId) {
throw new Meteor.Error('Creatures.methods.insert.denied',
'You need to be logged in to insert a creature');
}
assertUserHasPaidBenefits(this.userId);
// Create the creature document
let creatureId = Creatures.insert({
owner: this.userId,
});
CreatureProperties.insert({
slotTags: ['base'],
quantityExpected: 1,
type: 'propertySlot',
name: 'Base',
description: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.',
hideWhenFull: true,
parent: {collection: 'creatures', id: creatureId},
ancestors: [{collection: 'creatures', id: creatureId}],
order: 0,
tags: [],
spaceLeft: 1,
totalFilled: 0,
});
this.unblock();
return creatureId;
},
});
const updateCreature = new ValidatedMethod({
name: 'creatures.update',
validate({_id, path}){
if (!_id) return false;
// Allowed fields
let allowedFields = [
'name',
'alignment',
'gender',
'picture',
'avatarPicture',
'color',
'settings',
];
if (!allowedFields.includes(path[0])){
throw new Meteor.Error('Creatures.methods.update.denied',
'This field can\'t be updated using this method');
}
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, value}) {
let creature = Creatures.findOne(_id);
assertEditPermission(creature, this.userId);
if (value === undefined || value === null){
Creatures.update(_id, {
$unset: {[path.join('.')]: 1},
});
} else {
Creatures.update(_id, {
$set: {[path.join('.')]: value},
});
}
},
});
export default Creatures;
export { CreatureSchema, insertCreature, updateCreature };

View File

@@ -0,0 +1,28 @@
import SimpleSchema from 'simpl-schema';
let Parties = new Mongo.Collection('parties');
let partySchema = new SimpleSchema({
name: {
type: String,
defaultValue: 'New Party',
trim: false,
optional: true,
},
creatures: {
type: Array,
defaultValue: [],
},
'creatures.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
owner: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
});
Parties.attachSchema(partySchema);
export default Parties;

View File

@@ -0,0 +1,13 @@
import spendResources from '/imports/api/creature/actions/spendResources.js'
import embedInlineCalculations from '/imports/api/creature/computation/afterComputation/embedInlineCalculations.js';
export default function applyAction({prop, log}){
let content = { name: prop.name };
if (prop.summary){
content.description = embedInlineCalculations(
prop.summary, prop.summaryCalculations
);
}
log.content.push(content);
spendResources({prop, log});
}

View File

@@ -0,0 +1,55 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
import damagePropertiesByName from '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js';
export default function applyAdjustment({
prop,
creature,
targets,
actionContext,
log
}){
let damageTargets = prop.target === 'self' ? [creature] : targets;
let scope = {
...creature.variables,
...actionContext,
};
var {result, context} = evaluateString({
string: prop.amount,
scope,
fn: 'reduce'
});
context.errors.forEach(e => {
log.content.push({
name: 'Attribute damage error',
error: e.message || e.toString(),
});
});
if (damageTargets) {
damageTargets.forEach(target => {
if (prop.target === 'each'){
({result} = evaluateString({
string: prop.amount,
scope,
fn: 'reduce'
}));
}
damagePropertiesByName.call({
creatureId: target._id,
variableName: prop.stat,
operation: prop.operation || 'increment',
value: result.value,
});
log.content.push({
name: 'Attribute damage',
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
result: `${result.isNumber ? -result.value : result.toString()}`,
});
});
} else {
log.content.push({
name: 'Attribute damage',
resultPrefix: `${prop.stat} ${prop.operation === 'set' ? 'set to' : ''}`,
result: `${result.isNumber ? -result.value : result.toString()}`,
});
}
}

View File

@@ -0,0 +1,23 @@
import roll from '/imports/parser/roll.js';
export default function applyAttack({
prop,
log,
actionContext,
creature,
}){
let value = roll(1, 20)[0];
actionContext.attackRoll = {value};
let criticalHitTarget = creature.variables.criticalHitTarget &&
creature.variables.criticalHitTarget.currentValue || 20;
let criticalHit = value >= criticalHitTarget;
if (criticalHit) actionContext.criticalHit = {value: true};
let result = value + prop.rollBonusResult;
actionContext.toHit = {value: result};
log.content.push({
name: criticalHit ? 'Critical Hit!' : 'To Hit',
resultPrefix: `1d20 [${value}] + ${prop.rollBonusResult} = `,
result,
});
}

View File

@@ -0,0 +1,61 @@
import {
setLineageOfDocs,
renewDocIds
} from '/imports/api/parenting/parenting.js';
import {setDocToLastOrder} from '/imports/api/parenting/order.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export default function applyBuff({
prop,
children,
creature,
targets = [],
//actionContext,
}){
let buffTargets = prop.target === 'self' ? [creature] : targets;
//let scope = {
// ...creature.variables,
// ...actionContext,
//};
// TODO
// If the target is not self, walk through all decendants and replace
// variables in calculations with their values from the creature scope
// If the target is self, replace all the target.x references with just x
// Then copy the decendants of the buff to the targets
prop.applied = true;
let propList = [prop];
function addChildrenToPropList(children){
children.forEach(child => {
propList.push(child.node);
addChildrenToPropList(child.children);
});
}
addChildrenToPropList(children);
let oldParent = {
id: prop.parent.id,
collection: prop.parent.collection,
};
buffTargets.forEach(target => {
copyNodeListToTarget(propList, target, oldParent);
});
}
function copyNodeListToTarget(propList, target, oldParent){
let ancestry = [{collection: 'creatures', id: target._id}];
setLineageOfDocs({
docArray: propList,
newAncestry: ancestry,
oldParent,
});
renewDocIds({
docArray: propList,
});
setDocToLastOrder({
collection: CreatureProperties,
doc: propList[0],
});
CreatureProperties.batchInsert(propList);
}

View File

@@ -0,0 +1,121 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js';
import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js';
import { CompilationContext } from '/imports/parser/parser.js';
export default function applyDamage({
prop,
creature,
targets,
actionContext,
log,
}){
let damageTargets = prop.target === 'self' ? [creature] : targets;
let scope = {
...creature.variables,
...actionContext,
};
// Add the target's variables to the scope
if (targets.length === 1){
scope.target = targets[0].variables;
}
// Determine if the hit is critical
let criticalHit = !!(
actionContext.criticalHit &&
actionContext.criticalHit.value &&
prop.damageType !== 'healing' // Can't critically heal
);
// Double the damage rolls if the hit is critical
let context = new CompilationContext({
doubleRolls: criticalHit,
});
// Compute the roll the first time, logging any errors
var {result} = evaluateString({
string: prop.amount,
scope,
fn: 'reduce',
context
});
// If the result is an error bail out now
if (result.constructor.name === 'ErrorNode'){
log.content.push({
name: 'Damage error',
error: result.toString(),
});
return;
}
// Memoise the damage suffix for the log
let suffix = (criticalHit ? ' critical ' : '') +
prop.damageType +
(prop.damageType !== 'healing' ? ' damage': '');
if (damageTargets && damageTargets.length) {
// Iterate through all the targets
damageTargets.forEach(target => {
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
// Reroll the damage if needed
if (prop.target === 'each'){
({result, context} = evaluateString({
string: prop.amount,
scope,
fn: 'reduce'
}));
}
// If the result is an error or not a number bail out now
if (result.constructor.name === 'ErrorNode' || !result.isNumber){
log.content.push({
name: 'Damage error',
error: result.toString(),
});
return;
}
// Deal the damage to the target
let damageDealt = dealDamage.call({
creatureId: target._id,
damageType: prop.damageType,
amount: result.value,
});
// Log the damage done
if (target._id === creature._id){
// Target is same as self, log damage as such
log.content.push({
name,
result: damageDealt,
details: suffix + ' to self',
});
} else {
log.content.push({
name,
resultPrefix: 'Dealt ',
result: damageDealt,
details: suffix + `${target.name && ' to '}${target.name}`,
});
// Log the damage received on that creature's log as well
insertCreatureLog.call({
log: {
content: [{
name,
resultPrefix: 'Recieved ',
result: damageDealt,
details: suffix,
}],
creatureId: target._id,
}
});
}
});
} else {
// There are no targets, just log the result
log.content.push({
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
result: result.toString(),
details: suffix,
});
}
}

View File

@@ -0,0 +1,81 @@
import applyAction from '/imports/api/creature/actions/applyAction.js';
import applyAdjustment from '/imports/api/creature/actions/applyAdjustment.js';
import applyAttack from '/imports/api/creature/actions/applyAttack.js';
import applyBuff from '/imports/api/creature/actions/applyBuff.js';
import applyDamage from '/imports/api/creature/actions/applyDamage.js';
import applyRoll from '/imports/api/creature/actions/applyRoll.js';
import applyToggle from '/imports/api/creature/actions/applyToggle.js';
import applySave from '/imports/api/creature/actions/applySave.js';
function applyProperty(options){
let prop = options.prop;
if (prop.type === 'buff'){
// ignore only applied buffs, don't apply them again
if (prop.applied === true){
return false;
}
// Only ignore toggles if they wont be computed
} else if (prop.type === 'toggle') {
if (prop.disabled) return false;
if (prop.enabled) return true;
if (!prop.condition) return false;
// Ignore inactive props of other types
} else if (prop.deactivatedBySelf === true){
return false;
}
switch (prop.type){
case 'action':
case 'spell':
applyAction(options);
break;
case 'attack':
applyAction(options);
applyAttack(options);
break;
case 'damage':
applyDamage(options);
break;
case 'adjustment':
applyAdjustment(options);
break;
case 'buff':
applyBuff(options);
return false;
case 'toggle':
return applyToggle(options);
case 'roll':
applyRoll(options);
break;
case 'savingThrow':
return applySave(options);
}
return true;
}
function applyPropertyAndWalkChildren({prop, children, targets, ...options}){
let shouldKeepWalking = applyProperty({ prop, children, targets, ...options });
if (shouldKeepWalking){
applyProperties({ forest: children, targets, ...options,});
}
}
export default function applyProperties({ forest, targets, ...options}){
forest.forEach(node => {
let prop = node.node;
let children = node.children;
if (shouldSplit(prop) && targets.length){
targets.forEach(target => {
let targets = [target]
applyPropertyAndWalkChildren({ targets, prop, children, ...options});
});
} else {
applyPropertyAndWalkChildren({prop, children, targets, ...options});
}
});
}
function shouldSplit(prop){
if (prop.target === 'each'){
return true;
}
}

View File

@@ -0,0 +1,26 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
export default function applyRoll({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
var {result} = evaluateString({
string: prop.roll,
scope,
fn: 'reduce'
});
if (result.isNumber){
actionContext[prop.variableName] = result.value;
}
log.content.push({
name: prop.name,
resultPrefix: prop.variableName + ' = ' + prop.roll + ' = ',
result: result.toString(),
});
}

View File

@@ -0,0 +1,77 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
import CreaturesProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import roll from '/imports/parser/roll.js';
export default function applySave({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
try {
// Calculate the DC
var {result} = evaluateString({
string: prop.dc,
scope,
fn: 'reduce'
});
let dc = result.value;
log.content.push({
name: prop.name,
resultPrefix: ' DC ',
result: result.toString(),
});
if (prop.target === 'self'){
let save = CreaturesProperties.findOne({
'ancestors.id': creature._id,
type: 'skill',
skillType: 'save',
variableName: prop.stat,
removed: {$ne: true},
inactive: {$ne: true},
});
if (!save){
log.content.push({
error: 'No saving throw found: ' + prop.stat,
});
return;
}
let value, values, resultPrefix;
if (save.advantage === 1){
values = roll(2, 20).sort().reverse();
value = values[0];
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
} else if (save.advantage === -1){
values = roll(2, 20).sort();
value = values[0];
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
} else {
values = roll(1, 20);
value = values[0];
resultPrefix = `1d20 [${value}] + ${save.value} = `
}
actionContext.savingThrowRoll = {value};
let result = value + save.value;
actionContext.savingThrow = {value: result};
let saveSuccess = result >= dc;
log.content.push({
name: 'Save',
resultPrefix,
result,
details: saveSuccess ? 'Passed' : 'Failed'
});
return !saveSuccess;
} else {
// TODO
return true;
}
} catch (e){
log.content.push({
error: e.toString(),
});
}
}

View File

@@ -0,0 +1,34 @@
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
export default function applyToggle({
prop,
creature,
actionContext,
log,
}){
let scope = {
...creature.variables,
...actionContext,
};
if (Number.isFinite(+prop.condition)){
return !!+prop.condition;
}
var {result} = evaluateString({
string: prop.condition,
scope,
fn: 'reduce'
});
if (result.constructor.name === 'ErrorNode') {
log.content.push({
name: 'Toggle error',
error: result.toString(),
});
return false;
}
log.content.push({
name: prop.name || 'Toggle',
resultPrefix: prop.condition + ' = ',
result: result.toString(),
});
return !!result.value;
}

View File

@@ -0,0 +1,79 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import { doActionWork } from '/imports/api/creature/actions/doAction.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
const castSpellWithSlot = new ValidatedMethod({
name: 'creatureProperties.castSpellWithSlot',
validate: new SimpleSchema({
spellId: SimpleSchema.RegEx.Id,
slotId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
targetId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
run({spellId, slotId, targetId}) {
let spell = CreatureProperties.findOne(spellId);
// Check permissions
let creature = getRootCreatureAncestor(spell);
assertEditPermission(creature, this.userId);
let target = undefined;
if (targetId) {
target = Creatures.findOne(targetId);
assertEditPermission(target, this.userId);
}
let slotLevel = spell.level || 0;
if (slotLevel !== 0){
let slot = CreatureProperties.findOne(slotId);
if (!slot){
throw new Meteor.Error('No slot',
'Slot not found to cast spell');
}
if (!slot.currentValue){
throw new Meteor.Error('No slot',
'Slot depleted');
}
if (!(slot.spellSlotLevelValue >= spell.level)){
throw new Meteor.Error('Slot too small',
'Slot is not large enough to cast spell');
}
slotLevel = slot.spellSlotLevelValue;
damagePropertyWork({
property: slot,
operation: 'increment',
value: 1,
});
}
doActionWork({
action: spell,
context: {slotLevel},
creature,
targets: [target],
method: this,
});
// Note this only recomputes the top-level creature, not the nearest one
recomputeCreatureByDoc(creature);
if (target){
recomputeCreatureByDoc(target);
}
},
});
export default castSpellWithSlot;

View File

@@ -0,0 +1,94 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import { nodesToTree } from '/imports/api/parenting/parenting.js';
import applyProperties from '/imports/api/creature/actions/applyProperties.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
const doAction = new ValidatedMethod({
name: 'creatureProperties.doAction',
validate: new SimpleSchema({
actionId: SimpleSchema.RegEx.Id,
targetIds: {
type: Array,
defaultValue: [],
maxCount: 10,
optional: true,
},
'targetIds.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
run({actionId, targetIds = []}) {
let action = CreatureProperties.findOne(actionId);
// Check permissions
let creature = getRootCreatureAncestor(action);
assertEditPermission(creature, this.userId);
let targets = [];
targetIds.forEach(targetId => {
let target = Creatures.findOne(targetId);
assertEditPermission(target, this.userId);
targets.push(target);
});
doActionWork({action, creature, targets, method: this});
// The acting creature might have used ammo
recomputeInventory(creature._id);
// The action might add properties which need to be activated
recomputeInactiveProperties(creature._id);
// recompute creatures
recomputeCreatureByDoc(creature);
targets.forEach(target => {
recomputeInactiveProperties(target._id);
recomputeCreatureByDoc(target);
});
},
});
export function doActionWork({
action,
creature,
targets,
context = {},
method
}){
// Create the log
let log = CreatureLogSchema.clean({
creatureId: creature._id,
creatureName: creature.name,
});
let decendantForest = nodesToTree({
collection: CreatureProperties,
ancestorId: action._id,
});
let startingForest = [{
node: action,
children: decendantForest,
}];
applyProperties({
forest: startingForest,
actionContext: context,
creature,
targets,
log,
});
insertCreatureLogWork({log, creature, method});
}
export default doAction;

View File

@@ -0,0 +1,56 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Creatures from '/imports/api/creature/Creatures.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import roll from '/imports/parser/roll.js';
const doCheck = new ValidatedMethod({
name: 'creature.doCheck',
validate: new SimpleSchema({
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
optional: true,
},
attributeName: {
type: String,
optional: true,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
run({creatureId, attributeName}) {
let creature = Creatures.findOne(creatureId);
assertEditPermission(creature, this.userId);
let bonus = getAttributeValue({creature, attributeName})
return doCheckWork({bonus});
},
});
function getAttributeValue({creature, attributeName}){
let att = creature.variables[attributeName];
if (!att) throw new Meteor.Error('No such attribute',
`This creature does not have a ${attributeName} property`);
let bonus = att.attributeType === 'ability'? att.modifier : att.value;
return bonus || 0;
}
export function doCheckWork({bonus, advantage = 0}){
let rolls = roll(2,20);
let chosenRoll;
if (advantage === 1){
chosenRoll = Math.max.apply(rolls);
} else if (advantage === -1){
chosenRoll = Math.min.apply(rolls);
} else {
chosenRoll = rolls[0];
}
let result = chosenRoll + bonus;
return {rolls, bonus, chosenRoll, result};
}
export default doCheck;

View File

@@ -0,0 +1,93 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
export default function spendResources({prop, log}){
// Check Uses
if (prop.usesUsed >= prop.usesResult){
throw new Meteor.Error('Insufficient Uses',
'This prop has no uses left');
}
// Resources
if (prop.insufficientResources){
throw new Meteor.Error('Insufficient Resources',
'This creature doesn\'t have sufficient resources to perform this prop');
}
// Items
let itemQuantityAdjustments = [];
let spendLog = [];
let gainLog = [];
prop.resources.itemsConsumed.forEach(itemConsumed => {
if (!itemConsumed.itemId){
throw new Meteor.Error('Ammo not selected',
'No ammo was selected for this prop');
}
let item = CreatureProperties.findOne(itemConsumed.itemId);
if (!item || item.ancestors[0].id !== prop.ancestors[0].id){
throw new Meteor.Error('Ammo not found',
'The prop\'s ammo was not found on the creature');
}
if (!item.equipped){
throw new Meteor.Error('Ammo not equipped',
'The selected ammo is not equipped');
}
if (!itemConsumed.quantity) return;
itemQuantityAdjustments.push({
property: item,
operation: 'increment',
value: itemConsumed.quantity,
});
let logName = item.name;
if (itemConsumed.quantity > 1 || itemConsumed.quantity < -1){
logName = item.plural || logName;
}
if (itemConsumed.quantity > 0){
spendLog.push(logName + ': ' + itemConsumed.quantity);
} else if (itemConsumed.quantity < 0){
gainLog.push(logName + ': ' + -itemConsumed.quantity);
}
});
// No more errors should be thrown after this line
// Now that we have confirmed that there are no errors, do actual work
//Items
itemQuantityAdjustments.forEach(adjustQuantityWork);
// Use uses
if (prop.usesResult){
CreatureProperties.update(prop._id, {
$inc: {usesUsed: 1}
}, {
selector: prop
});
log.content.push({
name: 'Uses left',
result: prop.usesResult - (prop.usesUsed || 0) - 1,
});
}
// Damage stats
prop.resources.attributesConsumed.forEach(attConsumed => {
if (!attConsumed.quantity) return;
let stat = CreatureProperties.findOne(attConsumed.statId);
damagePropertyWork({
property: stat,
operation: 'increment',
value: attConsumed.quantity,
});
if (attConsumed.quantity > 0){
spendLog.push(stat.name + ': ' + attConsumed.quantity);
} else if (attConsumed.quantity < 0){
gainLog.push(stat.name + ': ' + -attConsumed.quantity);
}
});
// Log all the spending
if (gainLog.length) log.content.push({
name: 'Gained',
description: gainLog.join('\n'),
});
if (spendLog.length) log.content.push({
name: 'Spent',
description: spendLog.join('\n'),
});
}

View File

@@ -0,0 +1,11 @@
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
export default function embedInlineCalculations(string, calculations){
if (!string) return '';
if (!calculations) return string;
let index = 0;
return string.replace(INLINE_CALCULATION_REGEX, substring => {
let comp = calculations && calculations[index++];
return (comp && 'result' in comp) ? comp.result : substring;
});
}

View File

@@ -0,0 +1,67 @@
import { parse, CompilationContext } from '/imports/parser/parser.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
//TODO replace constants with their parsed node
export default function evaluateString({string, scope, fn = 'compile', context}){
if (!context){
context = new CompilationContext({});
}
if (!string){
context.storeError('No string provided');
return {result: {value: string}, context};
}
if (!scope) context.storeError('No scope provided');
// Parse the string using mathjs
let node;
try {
node = parse(string);
} catch (e) {
context.storeError(e);
return {result: {value: string}, context};
}
node = replaceConstants({calc: node, context, scope});
let result = node[fn](scope, context);
return {result, context};
}
// Replace constants in the calc with the right ParseNodes
function replaceConstants({calc, context, scope}){
let constFailed = [];
calc = calc.replaceNodes(node => {
if (!(node instanceof SymbolNode)) return;
let constant = scope[node.name];
// replace constants that aren't overridden by stats or disabled by a toggle
if (constant && constant.type === 'constant'){
// Fail if the constant has errors
if (constant.errors && constant.errors.length){
constFailed.push(node.name);
return;
}
let parsedConstantNode;
try {
parsedConstantNode = parse(constant.calculation);
} catch(e){
constFailed.push(node.name);
return;
}
if (!parsedConstantNode) constFailed.push(node.name);
return parsedConstantNode;
}
});
constFailed.forEach(name => {
context.storeError({
type: 'error',
message: `${name} is a constant property with parsing errors`
});
});
let failed = !!constFailed.length;
if (failed){
calc = new ErrorNode({error: 'Failed to replace constants'});
}
return calc;
}

View File

@@ -0,0 +1,307 @@
import { includes, cloneDeep } from 'lodash';
import findAncestorByType from '/imports/api/creature/computation/engine/findAncestorByType.js';
// The computation memo is an in-memory data structure used only during the
// computation process
export default class ComputationMemo {
constructor(props, creature){
this.statsByVariableName = {};
this.constantsByVariableName = {};
this.constantsById = {};
this.extraStatsByVariableName = {};
this.statsById = {};
this.originalPropsById = {};
this.propsById = {};
this.skillsByAbility = {};
this.unassignedEffects = [];
this.classLevelsById = {};
this.classes = {};
this.togglesById = {};
this.toggleIds = new Set();
// Equipped items that might be used as ammo
this.equipmentById = {};
// Properties that have calculations, but don't impact other properties
this.endStepPropsById = {};
// First note all the ids of all the toggles
props.forEach((prop) => {
if (
prop.type === 'toggle'
) {
this.toggleIds.add(prop._id);
}
});
props.filter((prop) => {
if (
prop.type === 'toggle'
) {
this.addToggle(prop);
} else {
return true;
}
}).filter((prop) => {
if (
prop.type === 'attribute' ||
prop.type === 'skill'
) {
// Add all the stats
this.addStat(prop);
} else if (
prop.type === 'item'
) {
this.addEquipment(prop);
} else {
return true;
}
}).forEach((prop) => {
// Now add everything else
if (prop.type === 'effect'){
this.addEffect(prop);
} else if (prop.type === 'proficiency') {
this.addProficiency(prop);
} else if (prop.type === 'classLevel'){
this.addClassLevel(prop);
} else if (prop.type === 'constant'){
this.addConstant(prop);
} else {
this.addEndStepProp(prop);
}
});
for (let name in creature.denormalizedStats){
if (!this.statsByVariableName[name]){
this.statsByVariableName[name] = {
variableName: name,
value: creature.denormalizedStats[name],
computationDetails: propDetailsByType.denormalizedStat(),
}
}
}
}
addConstant(prop){
prop = this.registerProperty(prop);
this.constantsById[prop._id] = prop;
}
registerProperty(prop){
this.originalPropsById[prop._id] = cloneDeep(prop);
this.propsById[prop._id] = prop;
prop.dependencies = [];
prop.computationDetails = propDetails(prop);
prop.ancestors.forEach(ancestor => {
if (this.toggleIds.has(ancestor.id)){
prop.computationDetails.toggleAncestors.push(ancestor.id);
}
});
return prop;
}
addToggle(prop){
prop = this.registerProperty(prop);
this.togglesById[prop._id] = prop;
}
addClassLevel(prop){
prop = this.registerProperty(prop);
this.classLevelsById[prop._id] = prop;
}
addStat(prop){
let variableName = prop.variableName;
if (!variableName) return;
let existingStat = this.statsByVariableName[variableName];
if (existingStat){
existingStat.computationDetails.idsOfSameName.push(prop._id);
this.originalPropsById[prop._id] = cloneDeep(prop);
if (prop.baseValueCalculation){
existingStat.computationDetails.effects.push({
operation: 'base',
calculation: prop.baseValueCalculation,
stats: [variableName],
computationDetails: propDetailsByType.effect(),
statBase: true,
dependencies: [],
});
}
if (prop.baseProficiency){
existingStat.computationDetails.proficiencies.push({
value: prop.baseProficiency,
stats: [variableName],
computationDetails: propDetailsByType.proficiency(),
type: 'proficiency',
statBase: true,
dependencies: [],
});
}
} else {
prop = this.registerProperty(prop);
this.statsById[prop._id] = prop;
this.statsByVariableName[variableName] = prop;
if (
prop.type === 'skill' &&
isSkillCheck(prop) &&
prop.ability
){
this.addSkillToAbility(prop, prop.ability)
}
}
}
addSkillToAbility(prop, ability){
if (!this.skillsByAbility[ability]){
this.skillsByAbility[ability] = [];
}
this.skillsByAbility[ability].push(prop);
}
addEffect(prop){
prop = this.registerProperty(prop);
let targets = this.getEffectTargets(prop);
targets.forEach(target => {
if (target.computationDetails && target.computationDetails.effects){
target.computationDetails.effects.push(prop);
}
});
if (!targets.size){
this.unassignedEffects.push(prop);
}
}
getEffectTargets(prop){
let targets = new Set();
if (!prop.stats) return targets;
prop.stats.forEach((statName) => {
let target;
if (statName[0] === '#'){
target = findAncestorByType({
type: statName.slice(1),
prop,
memo: this
});
} else {
target = this.statsByVariableName[statName];
}
if (!target) return;
targets.add(target);
if (isSkillOperation(prop) && isAbility(target)){
let extras = this.skillsByAbility[statName] || [];
extras.forEach(ex =>{
// Only pass on ability effects to skills and checks
if (ex.skillType === 'skill' || ex.skillType === 'check'){
targets.add(ex)
}
});
}
});
return targets;
}
addProficiency(prop){
prop = this.registerProperty(prop);
let targets = this.getProficiencyTargets(prop);
targets.forEach(target => {
target.computationDetails.proficiencies.push(prop);
});
}
getProficiencyTargets(prop){
let targets = new Set();
if (!prop.stats) return targets;
prop.stats.forEach(statName => {
let target = this.statsByVariableName[statName];
if (!target) return;
targets.add(target);
if (isAbility(target)) {
let extras = this.skillsByAbility[statName] || [];
extras.forEach(ex =>{
// Only pass on ability proficiencies to skills and checks
if (ex.skillType === 'skill' || ex.skillType === 'check'){
targets.add(ex)
}
});
}
});
return targets;
}
addEquipment(prop){
prop = this.registerProperty(prop);
this.equipmentById[prop._id] = prop;
}
addEndStepProp(prop){
prop = this.registerProperty(prop);
this.endStepPropsById[prop._id] = prop;
}
}
function isAbility(prop){
return prop.type === 'attribute' &&
prop.attributeType === 'ability'
}
function isSkillCheck(prop){
return includes(['skill', 'check', 'save', 'utility'], prop.skillType);
}
const skillOperations = [
'advantage',
'disadvantage',
'passiveAdd',
'fail',
'conditional',
'rollBonus',
];
function isSkillOperation(prop){
return skillOperations.includes(prop.operation);
}
function propDetails(prop){
return propDetailsByType[prop.type] && propDetailsByType[prop.type]() ||
propDetailsByType.default();
}
const propDetailsByType = {
default(){
return {
toggleAncestors: [],
};
},
toggle(){
return {
computed: false,
busyComputing: false,
toggleAncestors: [],
};
},
attribute(){
return {
computed: false,
busyComputing: false,
effects: [],
toggleAncestors: [],
idsOfSameName: [],
};
},
skill(){
return {
computed: false,
busyComputing: false,
effects: [],
proficiencies: [],
toggleAncestors: [],
idsOfSameName: [],
};
},
effect(){
return {
computed: false,
busyComputing: false,
toggleAncestors: [],
};
},
classLevel(){
return {
computed: true,
toggleAncestors: [],
};
},
proficiency(){
return {
toggleAncestors: [],
};
},
denormalizedStat(){
return {
toggleAncestors: [],
};
}
}

View File

@@ -0,0 +1,102 @@
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import { union } from 'lodash';
export default class EffectAggregator{
constructor(stat, memo){
delete this.baseValueErrors;
if (stat.baseValueCalculation){
let {
result,
context,
dependencies
} = evaluateCalculation({
string: stat.baseValueCalculation,
prop: stat,
memo
});
this.statBaseValue = +result.value;
stat.dependencies = union(
stat.dependencies,
dependencies,
);
if (context.errors.length){
this.baseValueErrors = context.errors;
}
this.base = this.statBaseValue;
} else {
this.base = 0;
}
this.add = 0;
this.mul = 1;
this.min = Number.NEGATIVE_INFINITY;
this.max = Number.POSITIVE_INFINITY;
this.advantage = 0;
this.disadvantage = 0;
this.passiveAdd = undefined;
this.fail = 0;
this.set = undefined;
this.conditional = [];
this.rollBonus = [];
this.hasNoEffects = true;
}
addEffect(effect){
let result = effect.result;
if (this.hasNoEffects) this.hasNoEffects = false;
switch(effect.operation){
case 'base':
// Take the largest base value
this.base = result > this.base ? result : this.base;
if (effect.statBase){
if (this.statBaseValue === undefined || result > this.statBaseValue){
this.statBaseValue = result;
}
}
break;
case 'add':
// Add all adds together
this.add += result;
break;
case 'mul':
// Multiply the muls together
this.mul *= result;
break;
case 'min':
// Take the largest min value
this.min = result > this.min ? result : this.min;
break;
case 'max':
// Take the smallest max value
this.max = result < this.max ? result : this.max;
break;
case 'set':
// Take the highest set value
this.set = this.set === undefined || result > this.set ? result : this.set;
break;
case 'advantage':
// Sum number of advantages
this.advantage++;
break;
case 'disadvantage':
// Sum number of disadvantages
this.disadvantage++;
break;
case 'passiveAdd':
// Add all passive adds together
if (this.passiveAdd === undefined) this.passiveAdd = 0;
this.passiveAdd += result;
break;
case 'fail':
// Sum number of fails
this.fail++;
break;
case 'conditional':
// Store array of conditionals
this.conditional.push(result);
break;
case 'rollBonus':
// Store array of roll bonuses
this.rollBonus.push(result);
break;
}
}
}

View File

@@ -0,0 +1,25 @@
import computeToggle from '/imports/api/creature/computation/engine/computeToggle.js';
import { union } from 'lodash';
export default function applyToggles(prop, memo){
// If it used to be inactive delete those fields
if (prop.inactive) prop.inactive = undefined;
if (prop.deactivatedByAncestor) prop.deactivatedByAncestor = undefined;
if (prop.deactivatedByToggle) prop.deactivatedByToggle = undefined;
// Iterate through the toggle ancestors from oldest to nearest
prop.computationDetails.toggleAncestors.forEach(toggleId => {
let toggle = memo.togglesById[toggleId];
computeToggle(toggle, memo);
prop.dependencies = union(
prop.dependencies,
[toggle._id],
toggle.dependencies,
);
// Deactivate if the toggle is false
if (!toggle.toggleResult){
prop.inactive = true;
prop.deactivatedByAncestor = true;
prop.deactivatedByToggle = true;
}
});
}

View File

@@ -0,0 +1,183 @@
import computeStat from '/imports/api/creature/computation/engine/computeStat.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import { union } from 'lodash';
export default function combineStat(stat, aggregator, memo){
if (stat.type === 'attribute'){
combineAttribute(stat, aggregator, memo);
} else if (stat.type === 'skill'){
combineSkill(stat, aggregator, memo);
} else if (stat.type === 'damageMultiplier'){
combineDamageMultiplier(stat, memo);
}
}
function getAggregatorResult(stat, aggregator){
let result = (aggregator.base + aggregator.add) * aggregator.mul;
if (result < aggregator.min) {
result = aggregator.min;
}
if (result > aggregator.max) {
result = aggregator.max;
}
if (aggregator.set !== undefined) {
result = aggregator.set;
}
if (!stat.decimal && Number.isFinite(result)){
result = Math.floor(result);
}
return result;
}
function combineAttribute(stat, aggregator, memo){
stat.value = getAggregatorResult(stat, aggregator);
stat.baseValue = aggregator.statBaseValue;
stat.baseValueErrors = aggregator.baseValueErrors;
if (stat.attributeType === 'spellSlot'){
let {
result,
context,
dependencies
} = evaluateCalculation({
string: stat.spellSlotLevelCalculation,
memo,
prop: stat,
});
stat.spellSlotLevelValue = result.value;
stat.spellSlotLevelErrors = context.errors;
stat.dependencies = union(stat.dependencies, dependencies);
}
stat.currentValue = stat.value - (stat.damage || 0);
// Ability scores get modifiers
if (stat.attributeType === 'ability') {
stat.modifier = Math.floor((stat.currentValue - 10) / 2);
} else {
stat.modifier = undefined;
}
// Hit dice get constitution modifiers
stat.constitutionMod = undefined;
if (stat.attributeType === 'hitDice') {
let conStat = memo.statsByVariableName['constitution'];
if (conStat && 'modifier' in conStat){
stat.constitutionMod = conStat.modifier;
stat.dependencies = union(
stat.dependencies,
[conStat._id],
conStat.dependencies,
);
}
}
// Stats that have no effects can be hidden based on a sheet setting
stat.hide = aggregator.hasNoEffects &&
stat.baseValue === undefined ||
undefined
}
function combineSkill(stat, aggregator, memo){
// Skills are based on some ability Modifier
let ability = stat.ability && memo.statsByVariableName[stat.ability]
if (stat.ability && ability){
if (!ability.computationDetails.computed){
computeStat(ability, memo);
}
stat.abilityMod = ability.modifier;
stat.dependencies = union(
stat.dependencies,
[ability._id],
ability.dependencies,
);
} else {
stat.abilityMod = 0;
}
// Combine all the child proficiencies
stat.proficiency = stat.baseProficiency || 0;
for (let i in stat.computationDetails.proficiencies){
let prof = stat.computationDetails.proficiencies[i];
applyToggles(prof, memo);
if (
!prof.deactivatedByToggle &&
prof.value > stat.proficiency
){
stat.proficiency = prof.value;
stat.dependencies = union(
stat.dependencies,
[prof._id],
prof.dependencies,
);
}
}
// Get the character's proficiency bonus to apply
let profBonusStat = memo.statsByVariableName['proficiencyBonus'];
let profBonus = profBonusStat && profBonusStat.value;
if (typeof profBonus !== 'number' && memo.statsByVariableName['level']){
let levelProp = memo.statsByVariableName['level'];
let level = levelProp.value;
profBonus = Math.ceil(level / 4) + 1;
if (levelProp._id){
stat.dependencies = union(stat.dependencies, [levelProp._id]);
}
if (levelProp.dependencies){
stat.dependencies = union(stat.dependencies, levelProp.dependencies);
}
} else {
stat.dependencies = union(
stat.dependencies,
[profBonusStat._id],
profBonusStat.dependencies,
);
}
// Multiply the proficiency bonus by the actual proficiency
profBonus *= stat.proficiency;
// Base value
stat.baseValue = aggregator.statBaseValue;
stat.baseValueErrors = aggregator.baseValueErrors;
// Combine everything to get the final result
let result = (aggregator.base + stat.abilityMod + profBonus + aggregator.add) * aggregator.mul;
if (result < aggregator.min) result = aggregator.min;
if (result > aggregator.max) result = aggregator.max;
if (aggregator.set !== undefined) {
result = aggregator.set;
}
if (Number.isFinite(result)){
result = Math.floor(result);
}
stat.value = result;
// Advantage/disadvantage
if (aggregator.advantage && !aggregator.disadvantage){
stat.advantage = 1;
} else if (aggregator.disadvantage && !aggregator.advantage){
stat.advantage = -1;
} else {
stat.advantage = 0;
}
// Passive bonus
stat.passiveBonus = aggregator.passiveAdd;
// conditional benefits
stat.conditionalBenefits = aggregator.conditional;
// Roll bonuses
stat.rollBonus = aggregator.rollBonus;
// Forced to fail
stat.fail = aggregator.fail;
// Rollbonus
stat.rollBonuses = aggregator.rollBonus;
// Hide
stat.hide = aggregator.hasNoEffects &&
stat.baseValue === undefined &&
stat.proficiency == 0 ||
undefined;
}
function combineDamageMultiplier(stat){
if (stat.immunityCount) return 0;
let result;
if (stat.ressistanceCount && !stat.vulnerabilityCount){
result = 0.5;
} else if (!stat.ressistanceCount && stat.vulnerabilityCount){
result = 2;
} else {
result = 1;
}
stat.value = result;
}

View File

@@ -0,0 +1,12 @@
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
export default function computeConstant(constant, memo){
// Apply any toggles
applyToggles(constant, memo);
if (constant.deactivatedByToggle) return;
if (
!memo.constantsByVariableName[constant.variableName]
){
memo.constantsByVariableName[constant.variableName] = constant
}
}

View File

@@ -0,0 +1,55 @@
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { union } from 'lodash';
export default function computeEffect(effect, memo){
if (effect.computationDetails.computed) return;
if (effect.computationDetails.busyComputing){
// Trying to compute this effect again while it is already computing.
// We must be in a dependency loop.
effect.computationDetails.computed = true;
effect.result = NaN;
effect.computationDetails.busyComputing = false;
effect.computationDetails.error = 'dependencyLoop';
if (Meteor.isClient) console.warn('dependencyLoop', effect);
return;
}
// Before doing any work, mark this effect as busy
effect.computationDetails.busyComputing = true;
// Apply any toggles
applyToggles(effect, memo);
// Determine result of effect calculation
delete effect.errors;
if (!effect.calculation){
if(effect.operation === 'add' || effect.operation === 'base'){
effect.result = 0;
} else {
delete effect.result
}
} else if (Number.isFinite(+effect.calculation)){
effect.result = +effect.calculation;
} else if(effect.operation === 'conditional' || effect.operation === 'rollBonus'){
effect.result = effect.calculation;
} else if(_.contains(['advantage', 'disadvantage', 'fail'], effect.operation)){
effect.result = 1;
} else {
let {
result,
context,
dependencies,
} = evaluateCalculation({
string: effect.calculation,
prop: effect,
memo
});
effect.result = result.value;
effect.dependencies = union(effect.dependencies, dependencies);
if (context.errors.length){
effect.errors = context.errors;
}
}
effect.computationDetails.computed = true;
effect.computationDetails.busyComputing = false;
}

View File

@@ -0,0 +1,129 @@
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { union } from 'lodash';
export default function computeEndStepProperty(prop, memo){
applyToggles(prop, memo);
switch (prop.type){
case 'action':
case 'spell':
computeAction(prop, memo);
break;
case 'adjustment':
case 'damage':
computePropertyField(prop, memo, 'amount', 'compile');
break;
case 'attack':
computeAction(prop, memo);
computePropertyField(prop, memo, 'rollBonus');
break;
case 'savingThrow':
computePropertyField(prop, memo, 'dc');
break;
case 'spellList':
computePropertyField(prop, memo, 'maxPrepared');
computePropertyField(prop, memo, 'attackRollBonus');
computePropertyField(prop, memo, 'dc');
break;
case 'propertySlot':
computePropertyField(prop, memo, 'quantityExpected');
computePropertyField(prop, memo, 'slotCondition');
break;
case 'roll':
computePropertyField(prop, memo, 'roll', 'compile');
break;
}
}
function computeAction(prop, memo){
// Uses
let {
result,
context,
dependencies,
} = evaluateCalculation({ string: prop.uses, prop, memo});
prop.usesResult = result.value;
prop.dependencies = union(prop.dependencies, dependencies);
if (context.errors.length){
prop.usesErrors = context.errors;
} else {
delete prop.usesErrors;
}
prop.insufficientResources = undefined;
if (prop.usesUsed >= prop.usesResult){
prop.insufficientResources = true;
}
if (!prop.resources) return;
// Attributes consumed
prop.resources.attributesConsumed.forEach((attConsumed, i) => {
if (attConsumed.variableName){
let stat = memo.statsByVariableName[attConsumed.variableName];
prop.resources.attributesConsumed[i].statId = stat && stat._id;
prop.resources.attributesConsumed[i].statName = stat && stat.name;
let available = stat && stat.currentValue || 0;
prop.resources.attributesConsumed[i].available = available;
if (available < attConsumed.quantity){
prop.insufficientResources = true;
}
if (stat){
prop.dependencies = union(
prop.dependencies,
[stat._id],
stat.dependencies
);
}
}
});
// Items consumed
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
let item = itemConsumed.itemId ?
memo.equipmentById[itemConsumed.itemId] :
undefined;
let available = item ? item.quantity : 0;
prop.resources.itemsConsumed[i].available = available;
if (!item || available < itemConsumed.quantity){
prop.insufficientResources = true;
}
if (item){
prop.resources.itemsConsumed[i].itemId = item._id;
let name = item.name;
if (item.quantity !== 1 && item.plural){
name = item.plural;
}
if (name) prop.resources.itemsConsumed[i].itemName = name;
if (item.icon) prop.resources.itemsConsumed[i].itemIcon = item.icon;
if (item.color) prop.resources.itemsConsumed[i].itemColor = item.color;
prop.dependencies = union(
prop.dependencies,
[item._id],
item.dependencies
);
} else {
delete prop.resources.itemsConsumed[i].itemId;
delete prop.resources.itemsConsumed[i].itemName;
delete prop.resources.itemsConsumed[i].itemIcon;
delete prop.resources.itemsConsumed[i].itemColor;
}
});
}
function computePropertyField(prop, memo, fieldName, fn){
let {
result,
context,
dependencies,
} = evaluateCalculation({string: prop[fieldName], prop, memo, fn});
if (result instanceof ConstantNode){
prop[`${fieldName}Result`] = result.value;
} else {
prop[`${fieldName}Result`] = result.toString();
}
prop.dependencies = union(prop.dependencies, dependencies);
if (context.errors.length){
prop[`${fieldName}Errors`] = context.errors;
} else {
delete prop[`${fieldName}Errors`];
}
}

View File

@@ -0,0 +1,40 @@
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import { union } from 'lodash';
export default function computeInlineCalculations(prop, memo){
if (prop.summary){
computeInlineCalcsForField(prop, memo, 'summary');
}
if (prop.description){
computeInlineCalcsForField(prop, memo, 'description');
}
}
function computeInlineCalcsForField(prop, memo, field){
let string = prop[field];
let inlineComputations = [];
let matches = string.matchAll(INLINE_CALCULATION_REGEX);
for (let match of matches){
let calculation = match[1];
let {
result,
context,
dependencies,
} = evaluateCalculation({string: calculation, prop, memo, fn: 'compile'});
if (result instanceof ErrorNode){
result = '`Calculation Error`';
}
let computation = {
calculation,
result: result && result.toString(),
};
if (context.errors.length){
computation.errors = context.errors;
}
inlineComputations.push(computation);
prop.dependencies = union(prop.dependencies, dependencies);
}
prop[`${field}Calculations`] = inlineComputations;
}

View File

@@ -0,0 +1,66 @@
import { forOwn, has, union } from 'lodash';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
export default function computeLevels(memo){
computeClassLevels(memo);
computeTotalLevel(memo);
}
function computeClassLevels(memo){
forOwn(memo.classLevelsById, classLevel => {
applyToggles(classLevel, memo);
// class levels are mutually dependent
classLevel.dependencies = union(
classLevel.dependencies,
Object.keys(memo.classLevelsById)
);
if (classLevel.deactivatedByToggle) return;
let name = classLevel.variableName;
let stat = memo.statsByVariableName[name];
if (!stat){
memo.statsByVariableName[name] = classLevel;
memo.classes[name] = classLevel;
} else if (!has(stat, 'level')){
// Stat is overriden by an attribute
return;
} else if (stat.level < classLevel.level) {
memo.statsByVariableName[name] = classLevel;
memo.classes[name] = classLevel;
}
});
}
function computeTotalLevel(memo){
let currentLevel = memo.statsByVariableName['level'];
if (!currentLevel || currentLevel.deactivatedByToggle){
currentLevel = {
value: 0,
dependencies: [],
computationDetails: {
builtIn: true,
computed: true,
}
};
memo.statsByVariableName['level'] = currentLevel;
}
// bail out if overriden by an attribute
if (!currentLevel.computationDetails.builtIn) return;
let level = 0;
for (let name in memo.classes){
let cls = memo.classes[name];
level += cls.level || 0;
if (cls._id){
currentLevel.dependencies = union(
currentLevel.dependencies,
[cls._id]
)
}
if (cls.dependencies){
currentLevel.dependencies = union(
currentLevel.dependencies,
cls.dependencies,
)
}
}
currentLevel.value = level;
}

View File

@@ -0,0 +1,37 @@
import { each, forOwn } from 'lodash';
import computeLevels from '/imports/api/creature/computation/engine/computeLevels.js';
import computeStat from '/imports/api/creature/computation/engine/computeStat.js';
import computeEffect from '/imports/api/creature/computation/engine/computeEffect.js';
import computeToggle from '/imports/api/creature/computation/engine/computeToggle.js';
import computeEndStepProperty from '/imports/api/creature/computation/engine/computeEndStepProperty.js';
import computeInlineCalculations from '/imports/api/creature/computation/engine/computeInlineCalculations.js';
import computeConstant from '/imports/api/creature/computation/engine/computeConstant.js';
export default function computeMemo(memo){
// Compute all constants that could be used
forOwn(memo.constantsById, constant => {
computeConstant (constant, memo);
});
// Compute level
computeLevels(memo);
// Compute all stats, even if they are overriden
forOwn(memo.statsById, stat => {
computeStat (stat, memo);
});
// Compute effects which didn't end up targeting a stat
each(memo.unassignedEffects, effect => {
computeEffect(effect, memo);
});
// Compute toggles which didn't already get computed by dependencies
forOwn(memo.togglesById, toggle => {
computeToggle(toggle, memo);
});
// Compute end step properties
forOwn(memo.endStepPropsById, prop => {
computeEndStepProperty(prop, memo);
});
// Compute inline calculations
forOwn(memo.propsById, prop => {
computeInlineCalculations(prop, memo);
});
}

View File

@@ -0,0 +1,47 @@
import combineStat from '/imports/api/creature/computation/engine/combineStat.js';
import computeEffect from '/imports/api/creature/computation/engine/computeEffect.js';
import EffectAggregator from '/imports/api/creature/computation/engine/EffectAggregator.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { each, union } from 'lodash';
export default function computeStat(stat, memo){
// If the stat is already computed, skip it
if (stat.computationDetails.computed) return;
if (stat.computationDetails.busyComputing){
// Trying to compute this stat again while it is already computing.
// We must be in a dependency loop.
stat.computationDetails.computed = true;
stat.value = NaN;
stat.computationDetails.busyComputing = false;
stat.computationDetails.error = 'dependencyLoop';
if (Meteor.isClient) console.warn('dependencyLoop', stat);
return;
}
// Before doing any work, mark this stat as busy
stat.computationDetails.busyComputing = true;
// Apply any toggles
applyToggles(stat, memo);
// Compute and aggregate all the effects
let aggregator = new EffectAggregator(stat, memo)
each(stat.computationDetails.effects, (effect) => {
computeEffect(effect, memo);
if (effect.deactivatedByToggle) return;
if (effect._id){
stat.dependencies = union(
stat.dependencies,
[effect._id]
);
}
stat.dependencies = union(
stat.dependencies,
effect.dependencies
)
aggregator.addEffect(effect);
});
// Conglomerate all the effects to compute the final stat values
combineStat(stat, aggregator, memo);
// Mark the attribute as computed
stat.computationDetails.computed = true;
stat.computationDetails.busyComputing = false;
}

View File

@@ -0,0 +1,55 @@
import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js';
import applyToggles from '/imports/api/creature/computation/engine/applyToggles.js';
import { union } from 'lodash';
export default function computeToggle(toggle, memo){
if (toggle.computationDetails.computed) return;
if (toggle.computationDetails.busyComputing){
// Trying to compute this effect again while it is already computing.
// We must be in a dependency loop.
toggle.computationDetails.computed = true;
toggle.result = false;
toggle.computationDetails.busyComputing = false;
toggle.computationDetails.error = 'dependencyLoop';
if (Meteor.isClient) console.warn('dependencyLoop', toggle);
return;
}
// Before doing any work, mark this toggle as busy
toggle.computationDetails.busyComputing = true;
// Apply any parent toggles
applyToggles(toggle, memo);
// Do work
delete toggle.errors;
if (toggle.enabled){
toggle.toggleResult = true;
} else if (toggle.disabled){
toggle.toggleResult = false;
} else if (!toggle.condition){
toggle.toggleResult = false;
} else if (Number.isFinite(+toggle.condition)){
toggle.toggleResult = !!+toggle.condition;
} else {
let {
result,
context,
dependencies,
} = evaluateCalculation({string: toggle.condition, prop: toggle, memo});
toggle.toggleResult = !!result.value;
toggle.dependencies = union(
toggle.dependencies,
dependencies,
);
if (context.errors.length){
toggle.errors = context.errors;
}
}
if (!toggle.toggleResult){
toggle.inactive = true;
toggle.deactivatedBySelf = true;
toggle.deactivatedByToggle = true;
}
toggle.computationDetails.computed = true;
toggle.computationDetails.busyComputing = false;
}

View File

@@ -0,0 +1,137 @@
import computeStat from '/imports/api/creature/computation/engine/computeStat.js';
import { prettifyParseError, parse, CompilationContext } from '/imports/parser/parser.js';
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
import ConstantNode from '/imports/parser/parseTree/ConstantNode.js';
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
import findAncestorByType from '/imports/api/creature/computation/engine/findAncestorByType.js';
import { union } from 'lodash';
/* Convert a calculation into a constant output and errors*/
export default function evaluateCalculation({
string,
prop,
memo,
fn = 'reduce',
}){
let dependencies = [];
let context = new CompilationContext();
if (!string) return {
result: new ConstantNode({value: string, type: 'string'}),
context,
dependencies,
};
if (typeof string !== 'string'){
string = string.toString();
}
// Parse the string
let calc;
try {
calc = parse(string);
} catch (e) {
let error = prettifyParseError(e);
return {
result: new ErrorNode({context, error}),
context,
dependencies,
};
}
// Replace constants with their parsed constant
let replaceResults = replaceConstants({
calc, memo, prop, dependencies, context
});
dependencies = replaceResults.dependencies;
calc = replaceResults.calc;
if (replaceResults.failed){
return {
result: new ConstantNode({value: string, type: 'string'}),
context,
dependencies,
};
}
// Ensure all symbol nodes are defined and computed
dependencies = computeSymbols({calc, memo, prop, dependencies})
// Evaluate
let result = calc[fn](memo.statsByVariableName, context);
return {result, context, dependencies};
}
// Replace constants in the calc with the right ParseNodes
function replaceConstants({calc, memo, prop, dependencies, context}){
let constFailed = [];
calc = calc.replaceNodes(node => {
if (!(node instanceof SymbolNode)) return;
let stat, constant;
if (node.name[0] !== '#'){
stat = memo.statsByVariableName[node.name]
constant = memo.constantsByVariableName[node.name];
} else if (node.name === '#constant'){
constant = findAncestorByType({type: 'constant', prop, memo});
}
// replace constants that aren't overridden by stats or disabled by a toggle
if (constant && !constant.deactivatedByToggle && !stat){
dependencies = union(dependencies, [
constant._id,
...constant.dependencies
]);
// Fail if the constant has errors
if (constant.errors && constant.errors.length){
constFailed.push(node.name);
return;
}
let parsedConstantNode;
try {
parsedConstantNode = parse(constant.calculation);
} catch(e){
constFailed.push(node.name);
return;
}
if (!parsedConstantNode) constFailed.push(node.name);
return parsedConstantNode;
}
});
constFailed.forEach(name => {
context.storeError({
type: 'error',
message: `${name} is a constant property with parsing errors`
});
});
let failed = !!constFailed.length;
if (failed){
calc = new ErrorNode({error: 'Failed to replace constants'});
}
return { failed, dependencies, calc };
}
// Ensure all symbol nodes are defined and computed
function computeSymbols({calc, memo, prop, dependencies}){
calc.traverse(node => {
if (node instanceof SymbolNode || node instanceof AccessorNode){
let stat;
// References up the tree start with #
if (node.name[0] === '#'){
stat = findAncestorByType({type: node.name.slice(1), prop, memo});
memo.statsByVariableName[node.name] = stat;
} else {
stat = memo.statsByVariableName[node.name];
}
if (stat && stat.computationDetails && !stat.computationDetails.computed){
computeStat(stat, memo);
}
if (stat){
if (stat.dependencies){
dependencies = union(dependencies, [
stat._id || node.name,
...stat.dependencies
]);
} else {
dependencies = union(dependencies, [stat._id || node.name]);
}
}
}
});
return dependencies;
}

View File

@@ -0,0 +1,10 @@
export default function findAncestorByType({type, prop, memo}){
if (!prop || !prop.ancestors) return;
let ancestor;
for (let i = prop.ancestors.length - 1; i >= 0; i--){
ancestor = memo.propsById[prop.ancestors[i].id];
if (ancestor && ancestor.type === type){
return ancestor;
}
}
}

View File

@@ -0,0 +1,24 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export default function getComputationProperties(creatureId){
// Find all the relevant properties
return CreatureProperties.find({
'ancestors.id': creatureId,
removed: {$ne: true},
$or: [
// All active properties
{inactive: {$ne: true}},
// Unless they were deactivated because of a toggle
{deactivatedByToggle: true},
]
}, {
// Filter out fields never used by calculations
fields: {
icon: 0,
},
// Obey tree order
sort: {
order: 1,
}
}).fetch();
}

View File

@@ -0,0 +1,51 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { union } from 'lodash';
export default function getDependentProperties({
creatureId,
propertyIds,
propertiesDependedAponIds,
}){
// find ids of all dependant toggles that have conditions, even if inactive
let toggleIds = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'toggle',
removed: {$ne: true},
condition: { $exists: true },
dependencies: {$in: propertyIds},
}, {
fields: {_id: 1},
}).map(t => t._id);
// Find all the dependant properties
let props = CreatureProperties.find({
'ancestors.id': creatureId,
removed: {$ne: true},
dependencies: {$in: propertyIds},
$or: [
// All active properties
{inactive: {$ne: true}},
// All active and inactive toggles with conditions
// Same as {$in: toggleIds}, but should be slightly faster
{type: 'toggle', condition: { $exists: true }},
// All decendents of the above toggles
{'ancestors.id': {$in: toggleIds}},
]
}, { fields: {_id: 1, dependencies: 1} }).fetch();
// Add all the properties that changing props depend on, but haven't yet been
// included to make an array of every property we need
let allConnectedPropIds = [...propertyIds, ...propertiesDependedAponIds];
props.forEach(prop => {
allConnectedPropIds = union(
allConnectedPropIds,
prop.dependencies,
[prop._id]);
});
// Add on all the properties and the objects they depend apon
return CreatureProperties.find({
_id: {$in: allConnectedPropIds}
}, {
// Ignore fields not used in computations
fields: {icon: 0},
sort: {order: 1},
}).fetch();
}

View File

@@ -0,0 +1,125 @@
import { Meteor } from 'meteor/meteor'
import { isEqual, forOwn } from 'lodash';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import propertySchemasIndex from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
export default function writeAlteredProperties(memo){
let bulkWriteOperations = [];
// Loop through all properties on the memo
forOwn(memo.propsById, changed => {
let schema = propertySchemasIndex[changed.type];
if (!schema){
console.warn('No schema for ' + changed.type);
return;
}
let extraIds = changed.computationDetails.idsOfSameName;
let ids;
if (extraIds && extraIds.length){
ids = [changed._id, ...extraIds];
} else {
ids = [changed._id];
}
ids.forEach(id => {
let op = undefined;
let original = memo.originalPropsById[id];
let keys = [
'dependencies',
'inactive',
'deactivatedBySelf',
'deactivatedByAncestor',
'deactivatedByToggle',
...schema.objectKeys(),
];
op = addChangedKeysToOp(op, keys, original, changed);
if (op){
bulkWriteOperations.push(op);
}
});
});
writePropertiesSequentially(bulkWriteOperations);
}
function addChangedKeysToOp(op, keys, original, changed) {
// Loop through all keys that can be changed by computation
// and compile an operation that sets all those keys
for (let key of keys){
if (!isEqual(original[key], changed[key])){
if (!op) op = newOperation(original._id, changed.type);
let value = changed[key];
if (value === undefined){
// Unset values that become undefined
addUnsetOp(op, key);
} else {
// Set values that changed to something else
addSetOp(op, key, value);
}
}
}
return op;
}
function newOperation(_id, type){
let newOp = {
updateOne: {
filter: {_id},
update: {},
}
};
if (Meteor.isClient){
newOp.type = type;
}
return newOp;
}
function addSetOp(op, key, value){
if (op.updateOne.update.$set){
op.updateOne.update.$set[key] = value;
} else {
op.updateOne.update.$set = {[key]: value};
}
}
function addUnsetOp(op, key){
if (op.updateOne.update.$unset){
op.updateOne.update.$unset[key] = 1;
} else {
op.updateOne.update.$unset = {[key]: 1};
}
}
// We use this instead of bulkWriteProperties because it functions with latency
// compensation without needing to roll back changes, which causes multiple
// expensive redraws of the character sheet
function writePropertiesSequentially(bulkWriteOps){
bulkWriteOps.forEach(op => {
let updateOneOrMany = op.updateOne || op.updateMany;
CreatureProperties.update(updateOneOrMany.filter, updateOneOrMany.update, {
// The bulk code is bypassing validation, so do the same here
// selector: {type: op.type} // include this if bypass is off
bypassCollection2: true,
});
});
}
// This is more efficient on the database, but significantly less efficient
// in the UI because of incompatibility with latency compensation. If the
// duplicate redraws can be fixed, this is a strictly better way of processing
// writes
function bulkWriteProperties(bulkWriteOps){
if (!bulkWriteOps.length) return;
// bulkWrite is only available on the server
if (Meteor.isServer){
CreatureProperties.rawCollection().bulkWrite(
bulkWriteOps,
{ordered : false},
function(e){
if (e) {
console.error('Bulk write failed: ');
console.error(e);
}
}
);
} else {
writePropertiesSequentially(bulkWriteOps);
}
}

View File

@@ -0,0 +1,59 @@
import { pick, forOwn } from 'lodash';
import Creatures from '/imports/api/creature/Creatures.js';
import VERSION from '/imports/constants/VERSION.js';
export default function writeCreatureVariables(memo, creatureId, fullRecompute = true) {
const fields = [
'ability',
'abilityMod',
'advantage',
'attributeType',
'baseProficiency',
'baseValue',
'calculation',
'conditionalBenefits',
'currentValue',
'damage',
'decimal',
'fail',
'level',
'modifier',
'name',
'passiveBonus',
'proficiency',
'reset',
'resetMultiplier',
'rollBonuses',
'skillType',
'spellSlotLevelValue',
'type',
'value',
];
if (fullRecompute){
memo.creatureVariables = {};
forOwn(memo.statsByVariableName, (stat, variableName) => {
// Don't save context variables
if (variableName[0] === '#') return;
let condensedStat = pick(stat, fields);
memo.creatureVariables[variableName] = condensedStat;
});
forOwn(memo.constantsByVariableName, (stat, variableName) => {
let condensedStat = pick(stat, fields);
if (!memo.creatureVariables[variableName]){
memo.creatureVariables[variableName] = condensedStat;
}
});
Creatures.update(creatureId, {$set: {
variables: memo.creatureVariables,
computeVersion: VERSION,
}});
} else {
let $set = {};
forOwn(memo.statsByVariableName, (stat, variableName) => {
let condensedStat = pick(stat, fields);
$set[`variables.${variableName}`] = condensedStat;
});
Creatures.update(creatureId, {$set});
}
}

View File

@@ -0,0 +1,120 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import ComputationMemo from '/imports/api/creature/computation/engine/ComputationMemo.js';
import getComputationProperties from '/imports/api/creature/computation/engine/getComputationProperties.js';
import computeMemo from '/imports/api/creature/computation/engine/computeMemo.js';
import writeAlteredProperties from '/imports/api/creature/computation/engine/writeAlteredProperties.js';
import writeCreatureVariables from '/imports/api/creature/computation/engine/writeCreatureVariables.js';
import { recomputeDamageMultipliersById } from '/imports/api/creature/denormalise/recomputeDamageMultipliers.js';
import recomputeSlotFullness from '/imports/api/creature/denormalise/recomputeSlotFullness.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import getDependentProperties from '/imports/api/creature/computation/engine/getDependentProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
export const recomputeCreature = new ValidatedMethod({
name: 'creatures.recomputeCreature',
validate: new SimpleSchema({
charId: { type: String }
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({charId}) {
let creature = Creatures.findOne(charId);
// Permission
assertEditPermission(creature, this.userId);
// Work, call this direcly if you are already in a method that has checked
// for permission to edit a given character
recomputeCreatureById(charId);
},
});
export function recomputeCreatureById(creatureId){
let creature = Creatures.findOne(creatureId);
recomputeCreatureByDoc(creature);
}
/**
* This function is the heart of DiceCloud. It recomputes a creature's stats,
* distilling down effects and proficiencies into the final stats that make up
* a creature.
*
* Essentially this is a depth first tree traversal algorithm that computes
* stats' dependencies before computing stats themselves, while detecting
* dependency loops.
*
* At the moment it makes no effort to limit recomputation to just what was
* changed.
*
* Attempting to implement dependency management to limit recomputation to just
* change affected stats should only happen as a last resort, when this function
* can no longer be performed more efficiently, and server resources can not be
* expanded to meet demand.
*
* A brief overview:
* - Fetch the stats of the creature and add them to
* an object for quick lookup
* - Fetch the effects and proficiencies which apply to each stat and store them with the stat
* - Fetch the class levels and store them as well
* - Mark each stat and effect as uncomputed
* - Iterate over each stat in order and compute it
* - If the stat is already computed, skip it
* - If the stat is busy being computed, we are in a dependency loop, make it NaN and mark computed
* - Mark the stat as busy computing
* - Iterate over each effect which applies to the attribute
* - If the effect is not computed compute it
* - If the effect relies on another attribute, get its computed value
* - Recurse if that attribute is uncomputed
* - apply the effect to the attribute
* - Conglomerate all the effects to compute the final stat values
* - Mark the stat as computed
* - Write the computed results back to the database
*/
export function recomputeCreatureByDoc(creature){
const creatureId = creature._id;
let props = getComputationProperties(creatureId);
let computationMemo = new ComputationMemo(props, creature);
computeMemo(computationMemo);
writeAlteredProperties(computationMemo);
writeCreatureVariables(computationMemo, creatureId);
recomputeDamageMultipliersById(creatureId);
recomputeSlotFullness(creatureId);
return computationMemo;
}
export function recomputePropertyDependencies(property){
let creature = getRootCreatureAncestor(property);
recomputeCreatureByDependencies({
creature,
propertyIds: [property._id],
propertiesDependedAponIds: property.dependencies,
});
}
export function recomputeCreatureByDependencies({
creature,
propertyIds,
propertiesDependedAponIds
}){
let props = getDependentProperties({
creatureId: creature._id,
propertyIds,
propertiesDependedAponIds,
});
let computationMemo = new ComputationMemo(props, creature);
computeMemo(computationMemo);
writeAlteredProperties(computationMemo);
writeCreatureVariables(computationMemo, creature._id, false)
recomputeInactiveProperties(creature._id);
return computationMemo;
}

View File

@@ -0,0 +1,97 @@
import {computeCreature} from "./recomputeCreature.js";
import assert from "assert";
const makeEffect = function(operation, value){
let effect = {computed: false, result: 0, operation}
if (_.isFinite(value)){
effect.value = +value;
} else {
effect.calculation = value;
}
return effect;
}
describe('computeCreature', function () {
it('computes an aritrary creature', function () {
let char = {
atts: {
attribute1: {
computed: false,
busyComputing: false,
type: "attribute",
attributeType: "ability",
result: 0,
mod: 0, // The resulting modifier if this is an ability
base: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
effects: [
makeEffect("base", 10),
makeEffect("add", 5),
makeEffect("mul", 2),
],
},
attribute2: {
computed: false,
busyComputing: false,
type: "attribute",
result: 0,
mod: 0, // The resulting modifier if this is an ability
base: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
effects: [
makeEffect("base", "attribute1"),
makeEffect("max", 2),
],
},
},
skills: {
skill1: {
computed: false,
busyComputing: false,
type: "skill",
ability: "attribute1",
result: 0,
proficiency: 0,
add: 0,
mul: 1,
min: Number.NEGATIVE_INFINITY,
max: Number.POSITIVE_INFINITY,
advantage: 0,
disadvantage: 0,
passiveAdd: 0,
fail: 0,
conditional: 0,
effects: [],
proficiencies: [],
},
},
dms: {
dm1: {
computed: false,
busyComputing: false,
type: "damageMultiplier",
result: 0,
immunityCount: 0,
ressistanceCount: 0,
vulnerabilityCount: 0,
effects: [],
}
},
classes: {
Barbarian: {
level: 5,
},
},
level: 5,
};
char = computeCreature(char);
console.log(char);
assert(true);
});
});

View File

@@ -0,0 +1,29 @@
import Creatures from '/imports/api/creature/Creatures.js';
import {
assertEditPermission as editPermission,
assertViewPermission as viewPermission,
assertOwnership as ownership
} from '/imports/api/sharing/sharingPermissions.js';
function getCreature(creature, fields){
if (typeof creature === 'string'){
return Creatures.findOne(creature, {fields});
} else {
return creature;
}
}
export function assertOwnership(creature, userId){
creature = getCreature(creature, {owner: 1});
ownership(creature, userId);
}
export function assertEditPermission(creature, userId) {
creature = getCreature(creature, {owner: 1, writers: 1});
editPermission(creature, userId);
}
export function assertViewPermission(creature, userId) {
creature = getCreature(creature, {owner: 1, readers:1, writers: 1, public: 1});
viewPermission(creature, userId);
}

View File

@@ -0,0 +1,91 @@
import { Mongo } from 'meteor/mongo';
import SimpleSchema from 'simpl-schema';
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
import ChildSchema from '/imports/api/parenting/ChildSchema.js';
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
import propertySchemasIndex from '/imports/api/properties/computedPropertySchemasIndex.js';
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
let CreatureProperties = new Mongo.Collection('creatureProperties');
let CreaturePropertySchema = new SimpleSchema({
type: {
type: String,
allowedValues: Object.keys(propertySchemasIndex),
},
tags: {
type: Array,
defaultValue: [],
},
'tags.$': {
type: String,
},
disabled: {
type: Boolean,
optional: true,
},
icon: {
type: storedIconsSchema,
optional: true,
},
// Denormalised flag if this property is inactive on the sheet for any reason
// Including being disabled, or a decendent of a disabled property
inactive: {
type: Boolean,
optional: true,
index: 1,
},
// Denormalised flag if this property was made inactive by an inactive
// ancestor. True if this property has an inactive ancestor even if this
// property is itself inactive
deactivatedByAncestor: {
type: Boolean,
optional: true,
index: 1,
},
// Denormalised flag if this property was made inactive because of its own
// state
deactivatedBySelf: {
type: Boolean,
optional: true,
index: 1,
},
// Denormalised flag if this property was made inactive because of a toggle
// calculation. Either an ancestor toggle calculation or its own.
deactivatedByToggle: {
type: Boolean,
optional: true,
index: 1,
},
// Denormalised list of all properties or creatures this property depends on
dependencies: {
type: Array,
defaultValue: [],
index: 1,
},
'dependencies.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
});
for (let key in propertySchemasIndex){
let schema = new SimpleSchema({});
schema.extend(propertySchemasIndex[key]);
schema.extend(CreaturePropertySchema);
schema.extend(ColorSchema);
schema.extend(ChildSchema);
schema.extend(SoftRemovableSchema);
CreatureProperties.attachSchema(schema, {
selector: {type: key}
});
}
import '/imports/api/creature/creatureProperties/methods/index.js';
import '/imports/api/creature/actions/doAction.js';
import '/imports/api/creature/actions/castSpellWithSlot.js';
export default CreatureProperties;
export {
CreaturePropertySchema,
};

View File

@@ -0,0 +1,5 @@
import Creatures from '/imports/api/creature/Creatures.js';
export default function getRootCreatureAncestor(property){
return Creatures.findOne(property.ancestors[0].id);
}

View File

@@ -0,0 +1,69 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const adjustQuantity = new ValidatedMethod({
name: 'creatureProperties.adjustQuantity',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id,
operation: {
type: String,
allowedValues: ['set', 'increment']
},
value: Number,
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, operation, value}) {
// Permissions
let property = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Do work
adjustQuantityWork({property, operation, value});
// Changing quantity does not change dependencies, but recomputing the
// inventory changes many deps at once, so recompute fully
recomputeCreatureByDoc(rootCreature);
recomputeInventory(rootCreature._id);
},
});
export function adjustQuantityWork({property, operation, value}){
// Check if property has quantity
let schema = CreatureProperties.simpleSchema(property);
if (!schema.allowsKey('quantity')){
throw new Meteor.Error(
'Adjust quantity failed',
`Property of type "${property.type}" doesn't have a quantity`
);
}
if (operation === 'set'){
CreatureProperties.update(property._id, {
$set: {quantity: value}
}, {
selector: property
});
} else if (operation === 'increment'){
// value here is 'damage'
value = -value;
let currentQuantity = property.quantity;
if (currentQuantity + value < 0) value = -currentQuantity;
CreatureProperties.update(property._id, {
$inc: {quantity: value}
}, {
selector: property
});
}
}
export default adjustQuantity;

View File

@@ -0,0 +1,57 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import { recomputePropertyDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const damagePropertiesByName = new ValidatedMethod({
name: 'CreatureProperties.damagePropertiesByName',
validate: new SimpleSchema({
creatureId: SimpleSchema.RegEx.Id,
variableName: {
type: String,
},
operation: {
type: String,
allowedValues: ['set', 'increment']
},
value: Number,
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 20,
timeInterval: 5000,
},
run({creatureId, variableName, operation, value}) {
// Check permissions
let creature = Creatures.findOne(creatureId, {
fields: {
damageMultipliers: 1,
owner: 1,
readers: 1,
writers: 1,
},
});
assertEditPermission(creature, this.userId);
let lastProperty;
CreatureProperties.find({
'ancestors.id': creatureId,
variableName,
removed: {$ne: false},
inactive: {$ne: true},
}).forEach(property => {
// Check if property can take damage
let schema = CreatureProperties.simpleSchema(property);
if (!schema.allowsKey('damage')) return;
// Damage the property
damagePropertyWork({property, operation, value});
lastProperty = property;
});
if (lastProperty) recomputePropertyDependencies(lastProperty);
}
});
export default damagePropertiesByName;

View File

@@ -0,0 +1,80 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { recomputePropertyDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const damageProperty = new ValidatedMethod({
name: 'creatureProperties.damage',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id,
operation: {
type: String,
allowedValues: ['set', 'increment']
},
value: Number,
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 20,
timeInterval: 5000,
},
run({_id, operation, value}) {
// Check permissions
let property = CreatureProperties.findOne(_id);
if (!property) throw new Meteor.Error(
'Damage property failed', 'Property doesn\'t exist'
);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Check if property can take damage
let schema = CreatureProperties.simpleSchema(property);
if (!schema.allowsKey('damage')){
throw new Meteor.Error(
'Damage property failed',
`Property of type "${property.type}" can't be damaged`
);
}
let result = damagePropertyWork({property, operation, value});
// Dependencies can't be changed through damage, only recompute deps
recomputePropertyDependencies(property);
return result;
},
});
export function damagePropertyWork({property, operation, value}){
if (operation === 'set'){
let currentValue = property.value;
// Set represents what we want the value to be after damage
// So we need the actual damage to get to that value
let damage = currentValue - value;
// Damage can't exceed total value
if (damage > currentValue) damage = currentValue;
// Damage must be positive
if (damage < 0) damage = 0;
CreatureProperties.update(property._id, {
$set: {damage}
}, {
selector: property
});
return currentValue - damage;
} else if (operation === 'increment'){
let currentValue = property.value - (property.damage || 0);
let currentDamage = property.damage;
let increment = value;
// Can't increase damage above the remaining value
if (increment > currentValue) increment = currentValue;
// Can't decrease damage below zero
if (-increment > currentDamage) increment = -currentDamage;
CreatureProperties.update(property._id, {
$inc: {damage: increment}
}, {
selector: property
});
return increment;
}
}
export default damageProperty;

View File

@@ -0,0 +1,73 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import { recomputeCreatureByDependencies } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const dealDamage = new ValidatedMethod({
name: 'creatureProperties.dealDamage',
validate: new SimpleSchema({
creatureId: SimpleSchema.RegEx.Id,
damageType: {
type: String,
},
amount: Number,
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 20,
timeInterval: 5000,
},
run({creatureId, damageType, amount}) {
// permissions
let creature = Creatures.findOne(creatureId, {
fields: {
damageMultipliers: 1,
owner: 1,
readers: 1,
writers: 1,
},
});
assertEditPermission(creature, this.userId);
// Get all the health bars and do damage to them
let healthBars = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'attribute',
attributeType:'healthBar',
removed: {$ne: true},
inactive: {$ne: true},
}, {
sort: {order: -1},
});
let multiplier = creature.damageMultipliers[damageType];
if (multiplier === undefined) multiplier = 1;
let totalDamage = Math.floor(amount * multiplier);
let damageLeft = totalDamage;
if (damageType === 'healing') damageLeft = -totalDamage;
let propertyIds = [];
let propertiesDependedAponIds = [];
healthBars.forEach(healthBar => {
if (damageLeft === 0) return;
let damageAdded = damagePropertyWork({
property: healthBar,
operation: 'increment',
value: damageLeft,
});
damageLeft -= damageAdded;
propertyIds.push(healthBar._id);
propertiesDependedAponIds.push(...healthBar.dependencies);
});
recomputeCreatureByDependencies({
creature,
propertyIds,
propertiesDependedAponIds,
});
return totalDamage;
},
});
export default dealDamage;

View File

@@ -0,0 +1,33 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { insertPropertyWork } from '/imports/api/creature/creatureProperties/methods/insertProperty.js';
const duplicateProperty = new ValidatedMethod({
name: 'creatureProperties.duplicate',
validate: new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
}
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}) {
let creatureProperty = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(creatureProperty);
assertEditPermission(rootCreature, this.userId);
insertPropertyWork({
property: creatureProperty,
creature: rootCreature,
});
},
});
export default duplicateProperty;

View File

@@ -0,0 +1,72 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
import INVENTORY_TAGS from '/imports/constants/INVENTORY_TAGS.js';
export function getParentRefByTag(creatureId, tag){
let prop = CreatureProperties.findOne({
'ancestors.id': creatureId,
removed: {$ne: true},
inactive: {$ne: true},
tags: tag,
}, {
sort: {order: 1},
});
if (prop){
return {id: prop._id, collection: 'creatureProperties'};
} else {
return {id: creatureId, collection: 'creatures'};
}
}
// Equipping or unequipping an item will also change its parent
const equipItem = new ValidatedMethod({
name: 'creatureProperties.equip',
validate({_id, equipped}){
if (!_id) throw new Meteor.Error('No _id', '_id is required');
if (equipped !== true && equipped !== false) {
throw new Meteor.Error('No equipped', 'equipped is required to be true or false');
}
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, equipped}) {
let item = CreatureProperties.findOne(_id);
if (item.type !== 'item') throw new Meteor.Error('wrong type',
'Equip and unequip can only be performed on items');
let creature = getRootCreatureAncestor(item);
assertEditPermission(creature, this.userId);
CreatureProperties.update(_id, {
$set: {equipped},
}, {
selector: {type: 'item'},
});
let tag = equipped ? INVENTORY_TAGS.equipment : INVENTORY_TAGS.carried;
let parentRef = getParentRefByTag(creature._id, tag);
organizeDoc.call({
docRef: {
id: _id,
collection: 'creatureProperties',
},
parentRef,
order: Number.MAX_SAFE_INTEGER,
skipRecompute: true,
});
recomputeInactiveProperties(creature._id);
recomputeInventory(creature._id);
recomputeCreatureByDoc(creature);
},
});
export default equipItem;

View File

@@ -0,0 +1,14 @@
import '//imports/api/creature/creatureProperties/methods/adjustQuantity.js';
import '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js';
import '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import '/imports/api/creature/creatureProperties/methods/dealDamage.js';
import '/imports/api/creature/creatureProperties/methods/duplicateProperty.js';
import '/imports/api/creature/creatureProperties/methods/equipItem.js';
import '/imports/api/creature/creatureProperties/methods/insertProperty.js';
import '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
import '/imports/api/creature/creatureProperties/methods/pullFromProperty.js';
import '/imports/api/creature/creatureProperties/methods/pushToProperty.js';
import '/imports/api/creature/creatureProperties/methods/restoreProperty.js';
import '/imports/api/creature/creatureProperties/methods/selectAmmoItem.js';
import '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
import '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';

View File

@@ -0,0 +1,49 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { reorderDocs } from '/imports/api/parenting/order.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
const insertProperty = new ValidatedMethod({
name: 'creatureProperties.insert',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({creatureProperty}) {
let rootCreature = getRootCreatureAncestor(creatureProperty);
assertEditPermission(rootCreature, this.userId);
return insertPropertyWork({
property: creatureProperty,
creature: rootCreature,
});
},
});
export function insertPropertyWork({property, creature}){
delete property._id;
let _id = CreatureProperties.insert(property);
// Tree structure changed by insert, reorder the tree
reorderDocs({
collection: CreatureProperties,
ancestorId: creature._id,
});
// Inserting the active status of the property needs to be denormalised
recomputeInactiveProperties(creature._id);
// Recompute the inventory if it has changed
if (property.type === 'item' || property.type === 'container'){
recomputeInventory(creature._id);
}
// Inserting a creature property invalidates dependencies: full recompute
recomputeCreatureByDoc(creature);
return _id;
}
export default insertProperty;

View File

@@ -0,0 +1,110 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import { RefSchema } from '/imports/api/parenting/ChildSchema.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import {
setLineageOfDocs,
getAncestry,
renewDocIds
} from '/imports/api/parenting/parenting.js';
import { reorderDocs } from '/imports/api/parenting/order.js';
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
const insertPropertyFromLibraryNode = new ValidatedMethod({
name: 'creatureProperties.insertPropertyFromLibraryNode',
validate: new SimpleSchema({
nodeId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
parentRef: {
type: RefSchema,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({nodeId, parentRef}) {
// get the new ancestry for the properties
let {parentDoc, ancestors} = getAncestry({parentRef});
// Check permission to edit
let rootCreature;
if (parentRef.collection === 'creatures'){
rootCreature = parentDoc;
} else if (parentRef.collection === 'creatureProperties'){
rootCreature = getRootCreatureAncestor(parentDoc);
} else {
throw `${parentRef.collection} is not a valid parent collection`
}
assertEditPermission(rootCreature, this.userId);
// Fetch the library node and its decendents, provided they have not been
// removed
let node = LibraryNodes.findOne({
_id: nodeId,
removed: {$ne: true},
});
if (!node) throw `Node not found for nodeId: ${nodeId}`;
let oldParent = node.parent;
let nodes = LibraryNodes.find({
'ancestors.id': nodeId,
removed: {$ne: true},
}).fetch();
// The root node is first in the array of nodes
// It must get the first generated ID to prevent flickering
nodes = [node, ...nodes];
// re-map all the ancestors
setLineageOfDocs({
docArray: nodes,
newAncestry: ancestors,
oldParent,
});
// Give the docs new IDs without breaking internal references
renewDocIds({
docArray: nodes,
collectionMap: {'libraryNodes': 'creatureProperties'}
});
// Order the root node
setDocToLastOrder({
collection: CreatureProperties,
doc: node,
});
// Insert the creature properties
CreatureProperties.batchInsert(nodes);
// get the root inserted doc
let rootId = node._id;
// Tree structure changed by inserts, reorder the tree
reorderDocs({
collection: CreatureProperties,
ancestorId: rootCreature._id,
});
// The library properties need to denormalise which of them are inactive
recomputeInactiveProperties(rootId);
// Some of the library properties may be items or containers
recomputeInventory(rootCreature._id);
// Inserting a creature property invalidates dependencies: full recompute
recomputeCreatureByDoc(rootCreature);
// Return the docId of the last property, the inserted root property
return rootId;
},
});
export default insertPropertyFromLibraryNode;

View File

@@ -0,0 +1,36 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const pullFromProperty = new ValidatedMethod({
name: 'creatureProperties.pull',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, itemId}){
// Permissions
let property = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Do work
CreatureProperties.update(_id, {
$pull: {[path.join('.')]: {_id: itemId}},
}, {
selector: {type: property.type},
getAutoValues: false,
});
// TODO figure out if this method can change deps or not
recomputeCreatureByDoc(rootCreature);
// recomputePropertyDependencies(property);
}
});
export default pullFromProperty;

View File

@@ -0,0 +1,35 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const pushToProperty = new ValidatedMethod({
name: 'creatureProperties.push',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, value}){
// Permissions
let property = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Do work
CreatureProperties.update(_id, {
$push: {[path.join('.')]: value},
}, {
selector: {type: property.type},
});
// TODO figure out if this method can change deps or not
recomputeCreatureByDoc(rootCreature);
// recomputePropertyDependencies(property);
}
});
export default pushToProperty;

View File

@@ -0,0 +1,40 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { restore } from '/imports/api/parenting/softRemove.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
const restoreProperty = new ValidatedMethod({
name: 'creatureProperties.restore',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}){
// Permissions
let property = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Do work
restore({_id, collection: CreatureProperties});
// Items and containers might be restored
recomputeInventory(rootCreature._id);
// Parents active status may have changed while it was deleted
recomputeInactiveProperties(rootCreature._id);
// Changes dependency tree by restoring children
recomputeCreatureByDoc(rootCreature);
}
});
export default restoreProperty;

View File

@@ -0,0 +1,52 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const selectAmmoItem = new ValidatedMethod({
name: 'creatureProperties.selectAmmoItem',
validate: new SimpleSchema({
actionId: SimpleSchema.RegEx.Id,
itemId: SimpleSchema.RegEx.Id,
itemConsumedIndex: Number,
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({actionId, itemId, itemConsumedIndex}) {
// Permissions
let action = CreatureProperties.findOne(actionId);
let rootCreature = getRootCreatureAncestor(action);
assertEditPermission(rootCreature, this.userId);
// Check that this index has a document to edit
let itemConsumed = action.resources.itemsConsumed[itemConsumedIndex];
if (!itemConsumed){
throw new Meteor.Error('Resouce not found',
'Could not set ammo, because the ammo document was not found');
}
let itemToLink = CreatureProperties.findOne(itemId);
if (!itemToLink){
throw new Meteor.Error('Item not found',
'Could not set ammo: the item was not found');
}
let path = `resources.itemsConsumed.${itemConsumedIndex}.itemId`;
CreatureProperties.update(actionId, {
$set: {[path]: itemId}
}, {
selector: action,
});
// Changing the linked item does change the dependency tree
// TODO: We can predict exactly which deps will be affected instead of
// recomputing the entire creature
recomputeCreatureByDoc(rootCreature);
},
});
export default selectAmmoItem;

View File

@@ -0,0 +1,37 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { softRemove } from '/imports/api/parenting/softRemove.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
const softRemoveProperty = new ValidatedMethod({
name: 'creatureProperties.softRemove',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}){
// Permissions
let property = CreatureProperties.findOne(_id);
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
// Do work
softRemove({_id, collection: CreatureProperties});
// Potentially changes items and containers
recomputeInventory(rootCreature._id);
// Changes dependency tree by removing children
recomputeCreatureByDoc(rootCreature);
}
});
export default softRemoveProperty;

View File

@@ -0,0 +1,67 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
import recomputeInactiveProperties from '/imports/api/creature/denormalise/recomputeInactiveProperties.js';
import recomputeInventory from '/imports/api/creature/denormalise/recomputeInventory.js';
const updateCreatureProperty = new ValidatedMethod({
name: 'creatureProperties.update',
validate({_id, path}){
if (!_id) throw new Meteor.Error('No _id', '_id is required');
// We cannot change these fields with a simple update
switch (path[0]){
case 'type':
case 'order':
case 'parent':
case 'ancestors':
case 'damage':
throw new Meteor.Error('Permission denied',
'This property can\'t be updated directly');
}
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, value}) {
// Permission
let property = CreatureProperties.findOne(_id, {
fields: {type: 1, ancestors: 1}
});
let rootCreature = getRootCreatureAncestor(property);
assertEditPermission(rootCreature, this.userId);
let pathString = path.join('.');
let modifier;
// unset empty values
if (value === null || value === undefined){
modifier = {$unset: {[pathString]: 1}};
} else {
modifier = {$set: {[pathString]: value}};
}
CreatureProperties.update(_id, modifier, {
selector: {type: property.type},
});
// Some updates might cause other properties to become inactive
if ([
'applied', 'equipped', 'prepared', 'alwaysPrepared', 'disabled'
].includes(path[0])){
recomputeInactiveProperties(rootCreature._id);
}
if (property.type === 'item' || property.type === 'container'){
// Potentially changes items and containers
recomputeInventory(rootCreature._id);
}
// Updating a property is likely to change dependencies, do a full recompute
// denormalised stats might change, so fetch the creature again
recomputeCreatureById(rootCreature._id);
},
});
export default updateCreatureProperty;

View File

@@ -0,0 +1,12 @@
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
/**
* Recomputes all ancestor creatures of this property
*/
export default function recomputeCreaturesByProperty(property){
for (let ref of property.ancestors){
if (ref.collection === 'creatures') {
recomputeCreatureById.call(ref.id);
}
}
}

View File

@@ -0,0 +1,78 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import Creatures from '/imports/api/creature/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export const recomputeDamageMultipliers = new ValidatedMethod({
name: 'creatures.recomputeDamageMultipliers',
validate: new SimpleSchema({
creatureId: { type: String }
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({creatureId}) {
// Permission
assertEditPermission(creatureId, this.userId);
// Work, call this direcly if you are already in a method that has checked
// for permission to edit a given character
recomputeDamageMultipliersById(creatureId);
},
});
export function recomputeDamageMultipliersById(creatureId){
if (!creatureId) throw 'Creature ID is required';
let props = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'damageMultiplier',
removed: {$ne: true},
inactive: {$ne: true},
}, {
sort: {order: 1}
});
// Count of how many weakness, resistances and immunities each damage type has
let multipliersByName = {};
props.forEach(dm => {
dm.damageTypes.forEach(damageType => {
if (!multipliersByName[damageType]){
multipliersByName[damageType] = {
weaknesses: 0,
resistances: 0,
immunities: 0,
};
}
if (dm.value === 0){
multipliersByName[damageType].immunities++;
} else if (dm.value === 0.5){
multipliersByName[damageType].resistances++;
} else if (dm.value === 2){
multipliersByName[damageType].weaknesses++;
}
});
});
// Make an Object with keys of all the damage types that have a resulting
// immunity, weakness, or resistance
let damageMultipliers = {};
for (let damageType in multipliersByName){
let multiplier = multipliersByName[damageType];
if (multiplier.immunities){
damageMultipliers[damageType] = 0;
} else if (multiplier.resistances && !multiplier.weaknesses){
damageMultipliers[damageType] = 0.5;
} else if (multiplier.weaknesses && !multiplier.resistances){
damageMultipliers[damageType] = 2;
}
}
// Store the Object on the creature document
Creatures.update(creatureId, {$set: {damageMultipliers}});
}

View File

@@ -0,0 +1,75 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
export default function recomputeInactiveProperties(ancestorId){
let disabledFilter = {
'ancestors.id': ancestorId,
$or: [
{disabled: true}, // Everything can be disabled
{type: 'buff', applied: false}, // Buffs can be applied
{type: 'item', equipped: {$ne: true}},
{type: 'spell', prepared: {$ne: true}, alwaysPrepared: {$ne: true}},
],
};
let disabledIds = CreatureProperties.find(disabledFilter, {
fields: {_id: 1},
}).map(prop => prop._id);
// Deactivate relevant properties
// Inactive properties
CreatureProperties.update({
'ancestors.id': ancestorId,
'_id': {$in: disabledIds},
$or: [
{inactive: {$ne: true}},
{deactivatedBySelf: {$ne: true}},
{deactivatedByAncestor: true},
],
}, {
$set: {
inactive: true,
deactivatedBySelf: true,
},
$unset: {deactivatedByAncestor: 1},
}, {
multi: true,
selector: {type: 'any'},
});
// Decendants of inactive properties
CreatureProperties.update({
'ancestors.id': {$eq: ancestorId, $in: disabledIds},
$or: [
{inactive: {$ne: true}},
{deactivatedByAncestor: {$ne: true}},
],
}, {
$set: {
inactive: true,
deactivatedByAncestor: true,
},
}, {
multi: true,
selector: {type: 'any'},
});
// Remove inactive from all the properties that are inactive but shouldn't be
CreatureProperties.update({
'ancestors.id': {$eq: ancestorId, $nin: disabledIds},
'_id': {$nin: disabledIds},
// if it was a toggle responsible, we leave it alone
deactivatedByToggle: {$ne: true},
$or: [
{inactive: true},
{deactivatedByAncestor: true},
{deactivatedBySelf: true}
],
}, {
$unset: {
inactive: 1,
deactivatedByAncestor: 1,
deactivatedBySelf: 1,
},
}, {
multi: true,
selector: {type: 'any'},
});
}

View File

@@ -0,0 +1,111 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { nodesToTree } from '/imports/api/parenting/parenting.js';
export default function recomputeInventory(creatureId){
let inventoryForest = nodesToTree({
collection: CreatureProperties,
ancestorId: creatureId,
filter: {
type: {$in: ['container', 'item']},
},
deactivatedByAncestor: {$ne: true},
});
let containersToWrite = [];
let data = getChildrenInventoryData(inventoryForest, containersToWrite);
containersToWrite.forEach(container => {
CreatureProperties.update(container._id, {$set: {
contentsWeight: container.contentsWeight,
contentsValue: container.contentsValue,
}}, {selector: {type: 'container'}});
});
Creatures.update(creatureId, {$set: {
'denormalizedStats.weightTotal': data.weightTotal,
'denormalizedStats.weightEquipment': data.weightEquipment,
'denormalizedStats.weightCarried': data.weightCarried,
'denormalizedStats.valueTotal': data.valueTotal,
'denormalizedStats.valueEquipment': data.valueEquipment,
'denormalizedStats.valueCarried': data.valueCarried,
'denormalizedStats.itemsAttuned': data.itemsAttuned,
}});
return data;
}
function getChildrenInventoryData(forest, containersToWrite){
let data = {
weightTotal: 0,
weightEquipment: 0,
weightCarried: 0,
valueTotal: 0,
valueEquipment: 0,
valueCarried: 0,
itemsAttuned: 0,
}
forest.forEach(tree => {
let treeData = getInventoryData(tree, containersToWrite);
for (let key in data){
data[key] += treeData[key] || 0;
}
});
return data;
}
function getInventoryData(tree, containersToWrite){
let data = {
weightTotal: 0,
weightEquipment: 0,
weightCarried: 0,
valueTotal: 0,
valueEquipment: 0,
valueCarried: 0,
itemsAttuned: 0,
}
let childData = getChildrenInventoryData(tree.children, containersToWrite);
let node = tree.node;
if (node.type === 'container'){
data.weightTotal += node.weight || 0;
data.valueTotal += node.value || 0;
data.weightCarried += node.weight || 0;
data.valueCarried += node.value || 0;
storeContentsData(node, childData, containersToWrite);
} else if (node.type === 'item'){
data.weightTotal += (node.weight * node.quantity) || 0;
data.valueTotal += (node.value * node.quantity) || 0;
data.weightCarried += (node.weight * node.quantity) || 0;
data.valueCarried += (node.value * node.quantity) || 0;
if (node.equipped){
data.weightEquipment += (node.weight * node.quantity) || 0;
data.valueEquipment += (node.value * node.quantity) || 0;
}
if (node.attuned){
data.itemsAttuned += 1;
}
}
for (let key in data){
data[key] += childData[key];
}
if (node.contentsWeightless){
data.weightCarried = node.weight;
}
if (node.carried === false){
data.weightCarried = 0;
data.valueCarried = 0;
}
return data
}
function storeContentsData(node, childData, containersToWrite){
let newContentsWeight = childData.weightCarried
if (node.contentsWeight !== newContentsWeight){
node.contentsWeight = newContentsWeight;
node.contentsWeightChanged = true;
}
let newContentsValue = childData.valueCarried;
if (node.contentsValue !== newContentsValue){
node.contentsValue = newContentsValue;
node.contentsValueChanged = true;
}
if (node.contentsWeightChanged || node.contentsValueChanged){
containersToWrite.push(node);
}
}

View File

@@ -0,0 +1,43 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
// n + 1 database queries + n potential updates for n slots. Could be sped up.
export default function recomputeSlotFullness(ancestorId){
CreatureProperties.find({
'ancestors.id': ancestorId,
type: 'propertySlot',
}).forEach(slot => {
let children = CreatureProperties.find({
'parent.id': slot._id,
removed: {$ne: true},
}, {
fields: {
slotQuantityFilled: 1,
type: 1
}
}).fetch();
let totalFilled = 0;
children.forEach(child => {
if (child.type === 'slotFiller'){
totalFilled += child.slotQuantityFilled;
} else {
totalFilled++;
}
});
let spaceLeft;
let expected = slot.quantityExpectedResult;
if (typeof expected !== 'number'){
expected = 1;
}
if (expected === 0){
spaceLeft = null;
} else {
spaceLeft = expected - totalFilled;
}
if (slot.totalFilled !== totalFilled || slot.spaceLeft !== spaceLeft){
CreatureProperties.update(slot._id, {
$set: {totalFilled, spaceLeft},
}, {
selector: {type: 'propertySlot'}
});
}
});
}

View File

@@ -0,0 +1,197 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import Creatures from '/imports/api/creature/Creatures.js';
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
let Experiences = new Mongo.Collection('experiences');
let ExperienceSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
// The amount of XP this experience gives
xp: {
type: SimpleSchema.Integer,
optional: true,
min: 0,
},
// Setting levels instead of value grants whole levels
levels: {
type: SimpleSchema.Integer,
optional: true,
min: 0,
index: 1,
},
// The real-world date that it occured, usually sorted by date
date: {
type: Date,
autoValue: function() {
// If the date isn't set, set it to now
if (!this.isSet) {
return new Date();
}
},
index: 1,
},
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
});
Experiences.attachSchema(ExperienceSchema);
const insertExperienceForCreature = function({experience, creatureId, userId}){
assertEditPermission(creatureId, userId);
if (experience.xp){
Creatures.update(creatureId, {$inc: {
'denormalizedStats.xp': experience.xp
}});
}
if (experience.levels) {
Creatures.update(creatureId, {$inc: {
'denormalizedStats.milestoneLevels': experience.levels
}});
}
experience.creatureId = creatureId;
let id = Experiences.insert(experience);
recomputeCreatureById(creatureId);
return id;
};
const insertExperience = new ValidatedMethod({
name: 'experiences.insert',
validate: new SimpleSchema({
experience: {
type: ExperienceSchema.omit('creatureId'),
},
creatureIds: {
type: Array,
max: 12,
},
'creatureIds.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({experience, creatureIds}) {
let userId = this.userId;
if (!userId) {
throw new Meteor.Error('Experiences.methods.insert.denied',
'You need to be logged in to insert an experience');
}
let tier = getUserTier(this.userId);
if (!tier.paidBenefits){
throw new Meteor.Error('Experiences.methods.insert.denied',
`The ${tier.name} tier does not allow you to grant experience`);
}
let insertedIds = [];
creatureIds.forEach(creatureId => {
let id = insertExperienceForCreature({experience, creatureId, userId});
insertedIds.push(id);
});
return insertedIds;
},
});
const removeExperience = new ValidatedMethod({
name: 'experiences.remove',
validate: new SimpleSchema({
experienceId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({experienceId}) {
let userId = this.userId;
if (!userId) {
throw new Meteor.Error('Experiences.methods.remove.denied',
'You need to be logged in to remove an experience');
}
let tier = getUserTier(this.userId);
if (!tier.paidBenefits){
throw new Meteor.Error('Experiences.methods.remove.denied',
`The ${tier.name} tier does not allow you to remove an experience`);
}
let experience = Experiences.findOne(experienceId);
if (!experience) return;
let creatureId = experience.creatureId
assertEditPermission(creatureId, userId);
if (experience.xp){
Creatures.update(creatureId, {$inc: {
'denormalizedStats.xp': -experience.xp
}});
}
if (experience.levels) {
Creatures.update(creatureId, {$inc: {
'denormalizedStats.milestoneLevels': -experience.levels
}});
}
experience.creatureId = creatureId;
let numRemoved = Experiences.remove(experienceId);
recomputeCreatureById(creatureId);
return numRemoved;
},
});
const recomputeExperiences = new ValidatedMethod({
name: 'experiences.recompute',
validate: new SimpleSchema({
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({creatureId}) {
let userId = this.userId;
if (!userId) {
throw new Meteor.Error('Experiences.methods.recompute.denied',
'You need to be logged in to recompute a creature\'s experiences');
}
let tier = getUserTier(this.userId);
if (!tier.paidBenefits){
throw new Meteor.Error('Experiences.methods.recompute.denied',
`The ${tier.name} tier does not allow you to recompute a creature's experiences`);
}
assertEditPermission(creatureId, userId);
let xp = 0;
let milestoneLevels = 0;
Experiences.find({
creatureId
}, {
fields: {xp: 1, levels: 1}
}).forEach(experience => {
xp += experience.xp || 0;
milestoneLevels += experience.levels || 0;
});
Creatures.update(creatureId, {$set: {
'denormalizedStats.xp': xp,
'denormalizedStats.milestoneLevels': milestoneLevels
}});
recomputeCreatureById(creatureId);
},
});
export default Experiences;
export { ExperienceSchema, insertExperience, removeExperience, recomputeExperiences };

View File

@@ -0,0 +1,44 @@
import SimpleSchema from 'simpl-schema';
let ExperienceSchema = new SimpleSchema({
title: {
type: String,
optional: true,
},
// Potentially long description of the event
description: {
type: String,
optional: true,
},
// The real-world date that it occured
date: {
type: Date,
autoValue: function() {
// If the date isn't set, set it to now
if (!this.isSet) {
return new Date();
}
},
},
// The date in-world of this event
worldDate: {
type: String,
optional: true,
},
// Tags to better find this entry later
tags: {
type: Array,
defaultValue: [],
},
'tags.$': {
type: String,
},
// ID of the journal this entry belongs to
journalId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
}
});
export { ExperienceSchema };

View File

@@ -0,0 +1,231 @@
import SimpleSchema from 'simpl-schema';
import Creatures from '/imports/api/creature/Creatures.js';
import LogContentSchema from '/imports/api/creature/log/LogContentSchema.js';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import {assertEditPermission} from '/imports/api/creature/creaturePermissions.js';
import {
parse,
CompilationContext,
prettifyParseError
} from '/imports/parser/parser.js';
const PER_CREATURE_LOG_LIMIT = 100;
if (Meteor.isServer){
var sendWebhookAsCreature = require('/imports/server/discord/sendWebhook.js').sendWebhookAsCreature;
}
let CreatureLogs = new Mongo.Collection('creatureLogs');
let CreatureLogSchema = new SimpleSchema({
content: {
type: Array,
defaultValue: [],
maxCount: 25,
},
'content.$': {
type: LogContentSchema,
},
// The real-world date that it occured, usually sorted by date
date: {
type: Date,
autoValue: function() {
// If the date isn't set, set it to now
if (!this.isSet) {
return new Date();
}
},
index: 1,
},
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
index: 1,
},
creatureName: {
type: String,
optional: true,
},
});
CreatureLogs.attachSchema(CreatureLogSchema);
function removeOldLogs(creatureId){
// Find the first log that is over the limit
let firstExpiredLog = CreatureLogs.find({
creatureId
}, {
sort: {date: -1},
skip: PER_CREATURE_LOG_LIMIT,
});
// Remove all logs older than the one over the limit
CreatureLogs.remove({
creatureId,
date: {$lte: firstExpiredLog.date},
});
}
function logToMessageData(log){
let embed = {
fields: [],
};
log.content.forEach(c => {
let field = {};
let descriptionField = {};
if (c.name) field.name = c.name;
let valueArray = [];
if (c.error) valueArray.push(`*${c.error}*`);
if (c.resultPrefix) valueArray.push(`${c.resultPrefix}`);
if (c.result) valueArray.push(`\`${c.result}\``);
if (c.details) valueArray.push(c.details);
if (valueArray.length) field.value = valueArray.join(' ');
if (c.description){
if (!field.value){
field.value = c.description;
} else {
descriptionField.value = c.description;
}
}
if (field.name || field.value){
if (!field.name) field.name = '\u200b';
if (!field.value) field.value = '\u200b';
embed.fields.push(field);
}
if (descriptionField.value){
descriptionField.name = '\u200b';
embed.fields.push(descriptionField);
}
});
return { embeds: [embed] };
}
function logWebhook({log, creature}){
if (Meteor.isServer){
sendWebhookAsCreature({
creature,
data: logToMessageData(log),
});
}
}
const insertCreatureLog = new ValidatedMethod({
name: 'creatureLogs.methods.insertCreatureLog',
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
validate: new SimpleSchema({
log: CreatureLogSchema.omit('date'),
}).validator(),
run({log}){
const creatureId = log.creatureId;
const creature = Creatures.findOne(creatureId, {fields: {
readers: 1,
writers: 1,
owner: 1,
'settings.discordWebhook': 1,
name: 1,
avatarPicture: 1,
}});
assertEditPermission(creature, this.userId);
// Build the new log
let id = insertCreatureLogWork({log, creature, method: this})
return id;
},
});
export function insertCreatureLogWork({log, creature, method}){
// Build the new log
if (typeof log === 'string'){
log = {text: log};
}
log.date = new Date();
// Insert it
let id = CreatureLogs.insert(log);
if (Meteor.isServer){
method.unblock();
removeOldLogs(creature._id);
logWebhook({log, creature});
}
return id;
}
function equalIgnoringWhitespace(a, b){
if (typeof a !== 'string' || typeof b !== 'string') return a === b;
return a.replace(/\s/g,'') === b.replace(/\s/g, '');
}
const logRoll = new ValidatedMethod({
name: 'creatureLogs.methods.logForCreature',
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
validate: new SimpleSchema({
roll: {
type: String,
},
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
run({roll, creatureId}){
const creature = Creatures.findOne(creatureId, {fields: {
variables: 1,
readers: 1,
writers: 1,
owner: 1,
'settings.discordWebhook': 1,
name: 1,
avatarPicture: 1,
}});
assertEditPermission(creature, this.userId);
let logContent = []
let parsedResult = undefined;
try {
parsedResult = parse(roll);
} catch (e){
let error = prettifyParseError(e);
logContent.push({error});
}
if (parsedResult) try {
let rollContext = new CompilationContext();
let compiled = parsedResult.compile(creature.variables, rollContext);
let compiledString = compiled.toString();
if (!equalIgnoringWhitespace(compiledString, roll)) logContent.push({
details: roll
});
logContent.push({
details: compiledString
});
let rolled = compiled.roll(creature.variables, rollContext);
let rolledString = rolled.toString();
if (rolledString !== compiledString) logContent.push({
result: rolled.toString()
});
let result = rolled.reduce(creature.variables, rollContext);
let resultString = result.toString();
if (resultString !== rolledString) logContent.push({
result: resultString
});
} catch (e){
logContent = [{error: 'Calculation error'}];
}
const log = {
content: logContent,
creatureId,
date: new Date(),
};
let id = insertCreatureLogWork({log, creature, method: this});
return id;
},
});
export default CreatureLogs;
export { CreatureLogSchema, insertCreatureLog, logRoll};

View File

@@ -0,0 +1,58 @@
import SimpleSchema from 'simpl-schema';
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
import RollDetailsSchema from '/imports/api/properties/subSchemas/RollDetailsSchema.js';
let LogContentSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
error: {
type: String,
optional: true,
},
resultPrefix: {
type: String,
optional: true,
},
result: {
type: String,
optional: true,
},
expandedResult: {
type: String,
optional: true,
},
details: {
type: String,
optional: true,
},
description: {
type: String,
optional: true,
},
context: {
type: Object,
optional: true,
},
'context.errors':{
type: Array,
defaultValue: [],
},
'context.errors.$': {
type: ErrorSchema,
},
'context.rolls': {
type: Array,
defaultValue: [],
},
'context.rolls.$': {
type: RollDetailsSchema,
},
'context.doubleRolls': {
type: Boolean,
optional: true,
},
});
export default LogContentSchema;

View File

@@ -0,0 +1,52 @@
import {
assertEditPermission,
assertViewPermission,
assertOwnership,
} from '/imports/api/creature/creaturePermissions.js';
// Checks if the method has permission to run on the document. If the document
// has a charId, that creature is checked, otherwise if it has an _id and the
// collection is defined in the method options, that document is fetched to
// determine its charId, otherwise a getCharId method can be defined to perform
// a special search for the required creature
//
// Because this mixin injects the charId into argument objects that don't
// already contain it, it should always come last in the mixin list, so that it
// the outermost wrapper of the run function
export default function creaturePermissionMixin(methodOptions){
let assertPermission;
if (methodOptions.permission === 'owner'){
assertPermission = assertOwnership;
} else if (methodOptions.permission === 'edit'){
assertPermission = assertEditPermission;
} else if (methodOptions.permission === 'view'){
assertPermission = assertViewPermission;
} else {
throw "`permission` missing in method options";
}
let getCharId;
if (methodOptions.getCharId){
getCharId = methodOptions.getCharId;
} else if (methodOptions.collection) {
getCharId = function({_id}){
return methodOptions.collection.findOne(_id, {
fields: {charId: 1}
}).charId;
};
} else {
getCharId = function(){
throw "`getCharId` or `collection` missing in method options," +
" or {charId} missing in call";
};
}
let runFunc = methodOptions.run;
methodOptions.run = function(doc, ...rest){
// Store the charId on the doc for other mixins if it had to be fetched
doc.charId = doc.charId || getCharId.apply(this, arguments);
assertPermission(doc.charId, this.userId);
return runFunc.call(this, doc, ...rest);
};
return methodOptions;
}

View File

@@ -0,0 +1,59 @@
import {
updateChildren,
updateDescendants,
} from '/imports/api/parenting/parenting.js';
import { inheritedFields } from '/imports/api/parenting/ChildSchema.js';
import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js';
// This mixin can be safely applied to all update methods which are validated
// with the updateSchemaMixin. It will propagate updates to fields which
// are inherited and normalised on the parent or ancestor docs
// It should have neglible performance impact for updates that aren't inherited
function propagateInheritanceUpdate({_id, update}){
let childModifier = {};
let descendantModifier = {};
// For each operator
for (let operator of MONGO_OPERATORS){
// If the operator is in the update, for each field
if (update[operator]) for (let field in update[operator]){
// Get the top level field that was actually modified
const indexOfDot = field.indexOf('.');
let modifiedField;
if (indexOfDot !== -1) {
modifiedField = field.substring(0, indexOfDot);
} else {
modifiedField = field;
}
// If that field is updated and inherited
if (inheritedFields.has(modifiedField)){
// Perform the same update on the descendants
if (!childModifier[operator]) childModifier[operator] = {};
if (!descendantModifier[operator]) descendantModifier[operator] = {};
childModifier[operator][`parent.${field}`] = update[operator][field];
descendantModifier[operator][`ancestors.$.${field}`] = update[operator][field];
}
}
}
// Update the parent object of its children
updateChildren({
parentId: _id,
modifier: childModifier,
});
// Update the ancestors object of its descendants
updateDescendants({
ancestorId: _id,
modifier: descendantModifier,
});
}
export default function propagateInheritanceUpdateMixin(methodOptions){
let runFunc = methodOptions.run;
methodOptions.run = function({_id, update}){
const result = runFunc.apply(this, arguments);
propagateInheritanceUpdate({_id, update});
return result;
};
return methodOptions;
}

View File

@@ -0,0 +1,17 @@
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
export default function recomputeCreatureMixin(methodOptions){
let runFunc = methodOptions.run;
methodOptions.run = function({charId}){
const result = runFunc.apply(this, arguments);
if (
methodOptions.skipRecompute &&
methodOptions.skipRecompute.apply(this, arguments)
) {
return result;
}
recomputeCreatureById(charId);
return result;
};
return methodOptions;
}

View File

@@ -0,0 +1,27 @@
import SimpleSchema from 'simpl-schema';
import { setDocToLastOrder } from '/imports/api/parenting/order.js';
export function setDocToLastMixin(methodOptions){
// Make sure the doc has a charId
// This mixin should come before simpleSchemaMixin so that it can extend the
// schema before it is turned into a validate function
if (methodOptions.validate){
throw new Meteor.Error(`setDocToLastMixin should come before simpleSchemaMixin`);
}
methodOptions.schema = new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).extend(methodOptions.schema);
let collection = methodOptions.collection;
if (!collection){
throw new Meteor.Error("`collection` required in method options for setDocToLastMixin");
}
let runFunc = methodOptions.run;
methodOptions.run = function(doc){
setDocToLastOrder({collection, doc});
return runFunc.apply(this, arguments);
};
return methodOptions;
}

View File

@@ -0,0 +1,51 @@
// Copied from https://github.com/sethjgore/meteor-simple-schema-mixin
// and updated to simpl-schema npm package
import SimpleSchema from 'simpl-schema';
export default function simpleSchemaMixin(methodOptions) {
// If the user didn't give us a schema and they did give us a validate, assume
// that they are choosing to use the validate way of doing things in this call.
// If they've built a wrapper around ValidateMethod that includes this mixin
// all the time, this could happen semi-"intentionally". There may be times they
// just don't want to use a schema and have specified a "validate" option. So
// returning the unchanged options instead of an error seems proper.
if ((typeof methodOptions.schema === 'undefined'
&& typeof methodOptions.validate !== 'undefined')
|| (typeof methodOptions.schema !== 'undefined' && methodOptions.schema === null
&& typeof methodOptions.validate !== 'undefined' && methodOptions.validate !== null)) {
return methodOptions;
}
// If they truly gave us both... that just doesn't seem proper.
if (methodOptions.validate && methodOptions.validate !== null) {
throw new Meteor.Error(
'simpleSchemaMixin.options',
'"schema" and "validate" options cannot be used together');
}
// Note that setting them both null will make it through, defaulting to the
// schema = null behavior (enforce no args) instead of the validate = null
// behavior (do no validation).
const newOptions = methodOptions;
newOptions.schemaValidatorOptions =
newOptions.schemaValidatorOptions ||
{ clean: true, filter: false };
let simpleSchema;
if (!newOptions.schema || newOptions.schema === null) {
// Allow simply leaving off both the schema and validate specifications
// or setting them to "null" as a shorthand. In this case, unlike
// the straight default validate or typical coder's call to validator,
// we will ENFORCE the Method be called without parameters because of
// the "filter: false" above.
simpleSchema = new SimpleSchema({});
} else if (newOptions.schema instanceof SimpleSchema) {
// In this one case, we can save ourselves the time to make a schema out
// of the schema.
simpleSchema = newOptions.schema;
} else {
simpleSchema = new SimpleSchema(newOptions.schema);
}
newOptions.validate = simpleSchema.validator(newOptions.schemaValidatorOptions);
return newOptions;
};

View File

@@ -0,0 +1,67 @@
const argumentSchema = new SimpleSchema({
_id: SimpleSchema.RegEx.Id,
update: {
type: Object,
blackbox: true,
},
});
// Modified simpleSchemaMixin
import SimpleSchema from 'simpl-schema';
export default function updateSchemaMixin(methodOptions) {
// If the user didn't give us a schema and they did give us a validate, assume
// that they are choosing to use the validate way of doing things in this call.
if ((
typeof methodOptions.schema === 'undefined' &&
typeof methodOptions.validate !== 'undefined'
) || (
typeof methodOptions.schema !== 'undefined' &&
methodOptions.schema === null &&
typeof methodOptions.validate !== 'undefined' &&
methodOptions.validate !== null
)) {
return methodOptions;
}
// If they truly gave us both... that just doesn't seem proper.
if (methodOptions.validate && methodOptions.validate !== null) {
throw new Meteor.Error(
'simpleSchemaMixin.options',
'"schema" and "validate" options cannot be used together');
}
// Note that setting them both null will make it through, defaulting to the
// schema = null behavior (enforce no args) instead of the validate = null
// behavior (do no validation).
// Apply default validator options if none are provided
methodOptions.schemaValidatorOptions =
methodOptions.schemaValidatorOptions ||
{ clean: true, modifier: true };
// Make the update schema a SimpleSchema, if it isn't already
let updateSchema;
if (methodOptions.schema instanceof SimpleSchema) {
updateSchema = methodOptions.schema;
} else {
updateSchema = new SimpleSchema(methodOptions.schema);
}
// Set up the new validation
methodOptions.validate = function(args){
argumentSchema.validate(args);
updateSchema.validate(
{$set: args.update},
methodOptions.schemaValidatorOptions
);
};
// Give a default run function if one isn't supplied
if (!methodOptions.run){
methodOptions.run = function({_id, update}){
return methodOptions.collection.update(_id, {$set: update});
};
}
return methodOptions;
}

View File

@@ -0,0 +1,41 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { assertOwnership } from '/imports/api/creature/creaturePermissions.js';
import Creatures from '/imports/api/creature/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
import Experiences from '/imports/api/creature/experience/Experiences.js';
function removeRelatedDocuments(creatureId){
CreatureProperties.remove({'ancestors.id': creatureId});
CreatureLogs.remove({creatureId});
Experiences.remove({creatureId});
}
const removeCreature = new ValidatedMethod({
name: 'Creatures.methods.removeCreature', // DDP method name
validate: new SimpleSchema({
charId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({charId}) {
assertOwnership(charId, this.userId)
this.unblock();
removeCreatureWork(charId)
},
});
export function removeCreatureWork(creatureId){
Creatures.remove(creatureId);
removeRelatedDocuments(creatureId);
}
export default removeCreature;

View File

@@ -0,0 +1,116 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Creatures from '/imports/api/creature/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
import { recomputeCreatureById } from '/imports/api/creature/computation/methods/recomputeCreature.js';
const restCreature = new ValidatedMethod({
name: 'creature.methods.longRest',
validate: new SimpleSchema({
creatureId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
restType: {
type: String,
allowedValues: ['shortRest', 'longRest'],
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({creatureId, restType}) {
let creature = Creatures.findOne(creatureId, {
fields: {
owner: 1,
writers: 1,
settings: 1,
}
}) ;
// Need edit permissions
assertEditPermission(creature, this.userId);
// Long rests reset short rest properties as well
let resetFilter;
if (restType === 'shortRest'){
resetFilter = 'shortRest'
} else {
resetFilter = {$in: ['shortRest', 'longRest']}
}
// Only apply to active properties
let filter = {
'ancestors.id': creatureId,
reset: resetFilter,
removed: {$ne: true},
inactive: {$ne: true},
};
// update all attribute's damage
filter.type = 'attribute';
CreatureProperties.update(filter, {
$set: {damage: 0}
}, {
selector: {type: 'attribute'},
multi: true,
});
// Update all action-like properties' usesUsed
filter.type = {$in: [
'action',
'attack',
'spell'
]};
CreatureProperties.update(filter, {
$set: {usesUsed: 0}
}, {
selector: {type: 'action'},
multi: true,
});
// Reset half hit dice on a long rest, starting with the highest dice
if (restType === 'longRest'){
let hitDice = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'attribute',
attributeType: 'hitDice',
removed: {$ne: true},
inactive: {$ne: true},
}, {
fields: {
hitDiceSize: 1,
damage: 1,
value: 1,
}
}).fetch();
// Use a collator to do sorting in natural order
let collator = new Intl.Collator('en', {
numeric: true, sensitivity: 'base'
});
// Get the hit dice in decending order of hitDiceSize
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
hitDice.sort(compare);
// Get the total number of hit dice that can be recovered this rest
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.value || 0), 0);
let resetMultiplier = creature.settings.hitDiceResetMultiplier || 0.5;
let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1);
// recover each hit dice in turn until the recoverable amount is used up
let amountToRecover, resultingDamage;
hitDice.forEach(hd => {
if (!recoverableHd) return;
amountToRecover = Math.min(recoverableHd, hd.damage || 0);
if (!amountToRecover) return;
recoverableHd -= amountToRecover;
resultingDamage = hd.damage - amountToRecover;
CreatureProperties.update(hd._id, {
$set: {damage: resultingDamage}
}, {
selector: {type: 'attribute'},
});
});
}
recomputeCreatureById(creatureId);
},
});
export default restCreature;

View File

@@ -0,0 +1,28 @@
import MONGO_OPERATORS from '/imports/constants/MONGO_OPERATORS.js';
const hasAny = function(values){
for (let value of values){
if (this.has(value)){
return true;
}
}
return false;
};
// Returns a Set of fields the modifier changes
// The set has been extended with the "hasAny" function
export default function getModifierFields (modifier) {
let fields = new Set();
for (let operator of MONGO_OPERATORS){
if (modifier[operator]) for (let field in modifier[operator]){
const indexOfDot = field.indexOf('.');
if (indexOfDot !== -1) {
field = field.substring(0, indexOfDot);
}
fields.add(field);
}
}
fields.hasAny = hasAny;
return fields;
}

View File

@@ -0,0 +1,97 @@
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import { assertAdmin } from '/imports/api/sharing/sharingPermissions.js';
let Icons = new Mongo.Collection('icons');
let iconsSchema = new SimpleSchema({
name: {
type: String,
unique: true,
index: 1,
},
description: {
type: String,
optional: true,
},
tags: {
type: Array,
optional: true,
index: 1,
},
'tags.$': {
type: String,
},
shape: {
type: String,
},
});
if (Meteor.isServer) {
Icons._ensureIndex({
'name': 'text',
'description': 'text',
'tags': 'text',
});
}
const storedIconsSchema = new SimpleSchema({
name: {
type: String,
},
shape: {
type: String,
},
});
Icons.attachSchema(iconsSchema);
// This method does not validate icons against the schema, use wisely;
const writeIcons = new ValidatedMethod({
name: 'icons.write',
validate: null,
run(icons){
assertAdmin(this.userId);
if (Meteor.isServer){
this.unblock();
Icons.rawCollection().insert(icons, {ordered: false});
}
}
});
const findIcons = new ValidatedMethod({
name: 'icons.find',
validate: new SimpleSchema({
search: {
type: String,
max: 30,
optional: true,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 20,
timeInterval: 10000,
},
run({search}){
if (!search) return [];
if (!Meteor.isServer) return;
return Icons.find(
{ $text: {$search: search} },
{
// relevant documents have a higher score.
fields: {
score: { $meta: 'textScore' }
},
// `score` property specified in the projection fields above.
sort: {
score: { $meta: 'textScore' }
}
}
).fetch();
}
})
export { writeIcons, findIcons, storedIconsSchema };
export default Icons;

View File

@@ -0,0 +1,131 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import SharingSchema from '/imports/api/sharing/SharingSchema.js';
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
import { assertEditPermission, assertOwnership } from '/imports/api/sharing/sharingPermissions.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import { getUserTier } from '/imports/api/users/patreon/tiers.js'
/**
* Libraries are trees of library nodes where each node represents a character
* property.
*
* Libraries can be shared, have multiple readers and writers, and can be
* subscribed to.
*
* Permissions to library nodes are controlled by the libraries they belong to.
*/
let Libraries = new Mongo.Collection('libraries');
let LibrarySchema = new SimpleSchema({
name: {
type: String,
},
isDefault: {
type: Boolean,
optional: true,
},
});
LibrarySchema.extend(SharingSchema);
Libraries.attachSchema(LibrarySchema);
export default Libraries;
const insertLibrary = new ValidatedMethod({
name: 'libraries.insert',
mixins: [
simpleSchemaMixin,
],
schema: LibrarySchema.omit('owner', 'isDefault'),
run(library) {
if (!this.userId) {
throw new Meteor.Error('Libraries.methods.insert.denied',
'You need to be logged in to insert a library');
}
let tier = getUserTier(this.userId);
if (!tier.paidBenefits){
throw new Meteor.Error('Libraries.methods.insert.denied',
`The ${tier.name} tier does not allow you to insert a library`);
}
library.owner = this.userId;
return Libraries.insert(library);
},
});
const updateLibraryName = new ValidatedMethod({
name: 'libraries.updateName',
validate: new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.id
},
name: {
type: String,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, name}){
let library = Libraries.findOne(_id);
assertEditPermission(library, this.userId);
Libraries.update(_id, {$set: {name}});
},
});
const setLibraryDefault = new ValidatedMethod({
name: 'libraries.makeLibraryDefault',
validate: new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.id
},
isDefault: {
type: Boolean,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, isDefault}) {
if (!Meteor.users.isAdmin()){
throw new Meteor.Error('Permission denied', 'User must be admin to set libraries as default');
}
return Libraries.update(_id, {$set: {isDefault}});
},
});
const removeLibrary = new ValidatedMethod({
name: 'libraries.remove',
validate: new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.id
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}){
let library = Libraries.findOne(_id);
assertOwnership(library, this.userId);
this.unblock();
removeLibaryWork(_id)
}
});
export function removeLibaryWork(libraryId){
Libraries.remove(libraryId);
LibraryNodes.remove({'ancestors.id': libraryId});
}
export { LibrarySchema, insertLibrary, setLibraryDefault, updateLibraryName, removeLibrary };

View File

@@ -0,0 +1,203 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import SimpleSchema from 'simpl-schema';
import ColorSchema from '/imports/api/properties/subSchemas/ColorSchema.js';
import ChildSchema from '/imports/api/parenting/ChildSchema.js';
import propertySchemasIndex from '/imports/api/properties/propertySchemasIndex.js';
import Libraries from '/imports/api/library/Libraries.js';
import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js';
import { softRemove } from '/imports/api/parenting/softRemove.js';
import SoftRemovableSchema from '/imports/api/parenting/SoftRemovableSchema.js';
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
let LibraryNodes = new Mongo.Collection('libraryNodes');
let LibraryNodeSchema = new SimpleSchema({
type: {
type: String,
allowedValues: Object.keys(propertySchemasIndex),
},
tags: {
type: Array,
defaultValue: [],
},
'tags.$': {
type: String,
},
icon: {
type: storedIconsSchema,
optional: true,
}
});
// Set up server side search index
if (Meteor.isServer) {
LibraryNodes._ensureIndex({
'name': 'text',
'tags': 'text',
});
}
for (let key in propertySchemasIndex){
let schema = new SimpleSchema({});
schema.extend(LibraryNodeSchema);
schema.extend(ColorSchema);
schema.extend(propertySchemasIndex[key]);
schema.extend(ChildSchema);
schema.extend(SoftRemovableSchema);
LibraryNodes.attachSchema(schema, {
selector: {type: key}
});
}
function getLibrary(node){
if (!node) throw new Meteor.Error('No node provided');
let library = Libraries.findOne(node.ancestors[0].id);
if (!library) throw new Meteor.Error('Library does not exist');
return library;
}
function assertNodeEditPermission(node, userId){
let lib = getLibrary(node);
return assertEditPermission(lib, userId);
}
const insertNode = new ValidatedMethod({
name: 'libraryNodes.insert',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run(libraryNode) {
delete libraryNode._id;
assertNodeEditPermission(libraryNode, this.userId);
return LibraryNodes.insert(libraryNode);
},
});
const duplicateNode = new ValidatedMethod({
name: 'libraryNodes.duplicate',
validate: new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
}
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}) {
let libraryNode = LibraryNodes.findOne(_id);
assertNodeEditPermission(libraryNode, this.userId);
delete libraryNode._id;
return LibraryNodes.insert(libraryNode);
},
})
const updateLibraryNode = new ValidatedMethod({
name: 'libraryNodes.update',
validate({_id, path}){
if (!_id) return false;
// We cannot change these fields with a simple update
switch (path[0]){
case 'type':
case 'order':
case 'parent':
case 'ancestors':
return false;
}
},
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, value}) {
let node = LibraryNodes.findOne(_id);
assertNodeEditPermission(node, this.userId);
let pathString = path.join('.');
let modifier;
// unset empty values
if (value === null || value === undefined){
modifier = {$unset: {[pathString]: 1}};
} else {
modifier = {$set: {[pathString]: value}};
}
return LibraryNodes.update(_id, modifier, {
selector: {type: node.type},
});
},
});
const pushToLibraryNode = new ValidatedMethod({
name: 'libraryNodes.push',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, value}){
let node = LibraryNodes.findOne(_id);
assertNodeEditPermission(node, this.userId);
return LibraryNodes.update(_id, {
$push: {[path.join('.')]: value},
}, {
selector: {type: node.type},
});
}
});
const pullFromLibraryNode = new ValidatedMethod({
name: 'libraryNodes.pull',
validate: null,
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id, path, itemId}){
let node = LibraryNodes.findOne(_id);
assertNodeEditPermission(node, this.userId);
return LibraryNodes.update(_id, {
$pull: {[path.join('.')]: {_id: itemId}},
}, {
selector: {type: node.type},
getAutoValues: false,
});
}
});
const softRemoveLibraryNode = new ValidatedMethod({
name: 'libraryNodes.softRemove',
validate: new SimpleSchema({
_id: SimpleSchema.RegEx.Id
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run({_id}){
let node = LibraryNodes.findOne(_id);
assertNodeEditPermission(node, this.userId);
softRemove({_id, collection: LibraryNodes});
}
});
export default LibraryNodes;
export {
LibraryNodeSchema,
insertNode,
duplicateNode,
updateLibraryNode,
pullFromLibraryNode,
pushToLibraryNode,
softRemoveLibraryNode,
};

View File

@@ -0,0 +1,11 @@
import SimpleSchema from 'simpl-schema';
Blacklist = new Mongo.Collection("blacklist");
Schemas.Blacklist = new SimpleSchema({
userId: {
type: String,
},
});
Blacklist.attachSchema(Schemas.Blacklist);

View File

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

View File

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

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