Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d2de197a4 | ||
|
|
a3d790b47d | ||
|
|
efe6dd87db | ||
|
|
5b33a6e783 | ||
|
|
8730fab40b | ||
|
|
992776bb40 | ||
|
|
bc9ec4421c | ||
|
|
4c31ab601c | ||
|
|
c4e77c7eae | ||
|
|
2cd6e27f70 | ||
|
|
f6b2dde479 | ||
|
|
44da62a962 | ||
|
|
4e96047e90 | ||
|
|
212986ac37 | ||
|
|
877f516565 | ||
|
|
750022f0f1 | ||
|
|
614284c73d | ||
|
|
6528fc8bab | ||
|
|
020930b2e4 | ||
|
|
dcd76e06e1 | ||
|
|
8a58002415 | ||
|
|
535fcd77cf | ||
|
|
7c2aed26a4 | ||
|
|
fab052050a | ||
|
|
b7bdb141c8 | ||
|
|
0b8fabde14 | ||
|
|
3336e177d9 | ||
|
|
2e9440e325 | ||
|
|
e4ac400cbd | ||
|
|
f600999c5f | ||
|
|
0af905699a | ||
|
|
6e900cfaae | ||
|
|
1f42d3c622 | ||
|
|
ee453d968f | ||
|
|
0850e59b30 | ||
|
|
f3e44cf033 | ||
|
|
907f9d15d4 | ||
|
|
4c3d5d40dd | ||
|
|
ef0deb20aa | ||
|
|
b43ee08d75 | ||
|
|
7e7f1ec997 | ||
|
|
0f652f5c74 | ||
|
|
3e02875eaf | ||
|
|
b9a5230344 | ||
|
|
28780b96c3 | ||
|
|
bec0b33805 | ||
|
|
ad9ccbe7ef | ||
|
|
e2933c2df5 | ||
|
|
87583fdac6 | ||
|
|
68e1382aed | ||
|
|
7b62c82e32 | ||
|
|
6dd92586a4 | ||
|
|
b3d0db1f02 | ||
|
|
5f35c71c9d | ||
|
|
85baf4e5d1 | ||
|
|
15d797131e | ||
|
|
06ac9f70c8 | ||
|
|
471a3e274e | ||
|
|
d4031dc4a7 | ||
|
|
18e5ab3f21 | ||
|
|
c9fe2f17a0 | ||
|
|
818cb3905f | ||
|
|
64ef426035 | ||
|
|
2b188f1a8d | ||
|
|
08735ea4f7 | ||
|
|
bce1b85600 | ||
|
|
0d023e2ba3 | ||
|
|
dad575de64 | ||
|
|
cb648b4a28 | ||
|
|
1279137362 | ||
|
|
3c06529906 | ||
|
|
a3b0c6cafd | ||
|
|
a1d9f7f5bb | ||
|
|
3b03e9c71c | ||
|
|
0eea6f2386 | ||
|
|
8f8714d3e5 | ||
|
|
7c38e8d70a | ||
|
|
826859ca3f | ||
|
|
2ca13fbb56 | ||
|
|
6d801e0178 | ||
|
|
9ddac7d5cd | ||
|
|
7a6f751e30 | ||
|
|
2389768234 | ||
|
|
c76fe99148 | ||
|
|
53afaa4f37 | ||
|
|
3599b5fbc4 | ||
|
|
275fb1ca65 | ||
|
|
d5d937b04a | ||
|
|
aa554adbce | ||
|
|
cb739eb207 | ||
|
|
66df2ea4aa | ||
|
|
73d1419ee9 | ||
|
|
681ef614c7 | ||
|
|
2bdbcb2e79 | ||
|
|
c119fcfbb8 | ||
|
|
deb5db8657 | ||
|
|
49522580e3 | ||
|
|
5b50f20128 | ||
|
|
52fa97c952 | ||
|
|
e7a5ce8241 | ||
|
|
d1b9043e1f | ||
|
|
9ffc5649f7 | ||
|
|
15e6c12c03 | ||
|
|
e89b877326 | ||
|
|
aff2f1f438 | ||
|
|
71d1e9e9e8 | ||
|
|
0696fd8447 | ||
|
|
b2db33e0f3 | ||
|
|
0c2842b84a | ||
|
|
789658cfe7 | ||
|
|
3be3da777f | ||
|
|
0e53f157d2 | ||
|
|
24cc4fd2b1 | ||
|
|
1f0ea689dc | ||
|
|
11adf9da04 | ||
|
|
5ca81056f9 | ||
|
|
6fc469f934 | ||
|
|
0c7948afdd | ||
|
|
42ffc79499 | ||
|
|
0240209410 | ||
|
|
c4c1afa669 | ||
|
|
47ac090e9d | ||
|
|
aadc83391f | ||
|
|
54fb398056 | ||
|
|
073094f6dd | ||
|
|
bb95fe0d9a | ||
|
|
832ed0c1ff | ||
|
|
17a390a8aa | ||
|
|
e65a2db0d2 | ||
|
|
b3ef43eb70 | ||
|
|
be92ef224c | ||
|
|
6729bcad64 | ||
|
|
7af07e7ba3 | ||
|
|
f44914ab84 | ||
|
|
59c7eff46a | ||
|
|
2a332a2965 | ||
|
|
44a1daf6f8 | ||
|
|
ac23afac5d | ||
|
|
a411fb2b43 | ||
|
|
35b6fe20ae | ||
|
|
54f055d3ef | ||
|
|
2bacb296ba | ||
|
|
8b061f7aa9 | ||
|
|
6a84c83644 | ||
|
|
3227cd0934 | ||
|
|
089feae26f | ||
|
|
99c72d1e10 | ||
|
|
1e67afbe6f | ||
|
|
1cfec1ca45 | ||
|
|
09d1ac9ba3 | ||
|
|
834b9cf384 | ||
|
|
37291b347a | ||
|
|
efdfbeb59e | ||
|
|
a165f9b253 | ||
|
|
4a6fca98bc | ||
|
|
35464128a0 | ||
|
|
398f8a8a2a | ||
|
|
812a1784b2 | ||
|
|
8fa9cd0148 | ||
|
|
0e0662cc9a | ||
|
|
ad4e3f5b20 |
@@ -21,32 +21,40 @@
|
||||
"weight": 1
|
||||
},
|
||||
{
|
||||
"name": "Arrows (20)",
|
||||
"plural": "Arrows (20)",
|
||||
"libraryName": "Arrows (20)",
|
||||
"name": "Arrow",
|
||||
"plural": "Arrows",
|
||||
"description": "",
|
||||
"value": 1,
|
||||
"weight": 1
|
||||
"value": 0.05,
|
||||
"weight": 0.05,
|
||||
"quantity": 20
|
||||
},
|
||||
{
|
||||
"name": "Blowgun needles (5)",
|
||||
"plural": "Blowgun needles (5)",
|
||||
"libraryName": "Blowgun needles (5)",
|
||||
"name": "Blowgun needle",
|
||||
"plural": "Blowgun needles",
|
||||
"description": "",
|
||||
"value": 1,
|
||||
"weight": 1
|
||||
"value": 0.2,
|
||||
"weight": 0.2,
|
||||
"quantity": 5
|
||||
},
|
||||
{
|
||||
"name": "Crossbow bolts (20)",
|
||||
"plural": "Crossbow bolts (20)",
|
||||
"libraryName": "Crossbow bolts (20)",
|
||||
"name": "Crossbow bolt",
|
||||
"plural": "Crossbow bolts",
|
||||
"description": "",
|
||||
"value": 1,
|
||||
"weight": 1.5
|
||||
"value": 0.05,
|
||||
"weight": 0.075,
|
||||
"quantity": 20
|
||||
},
|
||||
{
|
||||
"name": "Sling bullets (20)",
|
||||
"plural": "Sling bullets (20)",
|
||||
"libraryName": "Sling bullets (20)",
|
||||
"name": "Sling bullet",
|
||||
"plural": "Sling bullets",
|
||||
"description": "",
|
||||
"value": 0.04,
|
||||
"weight": 1.5
|
||||
"value": 0.002,
|
||||
"weight": 0.075,
|
||||
"quantity": 20
|
||||
},
|
||||
{
|
||||
"name": "Antitoxin (vial)",
|
||||
@@ -651,11 +659,13 @@
|
||||
"weight": 3
|
||||
},
|
||||
{
|
||||
"name": "Spikes, iron (10)",
|
||||
"plural": "Spikes, iron (10)",
|
||||
"libraryName": "Spikes, iron (10)",
|
||||
"name": "Spike, iron",
|
||||
"plural": "Spikes, iron",
|
||||
"description": "",
|
||||
"value": 1,
|
||||
"weight": 5
|
||||
"value": 0.1,
|
||||
"weight": 0.5,
|
||||
"quantity": 10
|
||||
},
|
||||
{
|
||||
"name": "Spyglass",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
// This all gets run in the console by an admin.
|
||||
// Probably a good idea to reset the server after running big updates
|
||||
// Only do if the library doesn't exist yet
|
||||
id = Libraries.insert({
|
||||
_id: "SRDLibraryGA3XWsd",
|
||||
@@ -5,6 +7,8 @@ id = Libraries.insert({
|
||||
name: "SRD Library",
|
||||
});
|
||||
|
||||
// First copy-paste the JSON into your console like `items = <pasted JSON>`
|
||||
// First import, don't do this if the library is already populated
|
||||
_.each(items, (item) => {
|
||||
item.settings = {category: }; // "adventuringGear", "armor", "weapons", "tools"
|
||||
item.library = "SRDLibraryGA3XWsd"
|
||||
@@ -15,3 +19,38 @@ _.each(spells, (spell) => {
|
||||
spell.library = "SRDLibraryGA3XWsd"
|
||||
LibrarySpells.insert(spell)
|
||||
});
|
||||
|
||||
// Update the library using names as keys
|
||||
// Make sure you're subscribed to all item categories
|
||||
handles = _.map(["weapons", "armor", "adventuringGear", "tools"],
|
||||
category => Meteor.subscribe("standardLibraryItems", category)
|
||||
);
|
||||
// Wait until all the handles are ready
|
||||
handles.map(h => h.ready()); // must reaturn [...true]
|
||||
|
||||
_.each(items, (item) => {
|
||||
var existingItem = LibraryItems.findOne({
|
||||
library: "SRDLibraryGA3XWsd",
|
||||
name: item.name,
|
||||
});
|
||||
if (!existingItem) return;
|
||||
_.each(item.attacks, attack => Schemas.LibraryAttacks.clean(attack));
|
||||
LibraryItems.update(existingItem._id, {$set: item});
|
||||
});
|
||||
|
||||
// Make sure you're subscribed to all spell categories
|
||||
handles = _.map([0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
category => Meteor.subscribe("standardLibrarySpells", category)
|
||||
);
|
||||
// Wait until all the handles are ready
|
||||
handles.map(h => h.ready()); // must reaturn [...true]
|
||||
|
||||
_.each(spells, (spell) => {
|
||||
var existingSpell = LibrarySpells.findOne({
|
||||
library: "SRDLibraryGA3XWsd",
|
||||
name: spell.name,
|
||||
});
|
||||
if (!existingSpell) return;
|
||||
_.each(spell.attacks, attack => Schemas.LibraryAttacks.clean(attack));
|
||||
LibrarySpells.update(existingSpell._id, {$set: spell});
|
||||
});
|
||||
|
||||
@@ -13,3 +13,5 @@ notices-for-facebook-graph-api-2
|
||||
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
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
# but you can also edit it by hand.
|
||||
|
||||
iron:router
|
||||
accounts-password@1.3.3
|
||||
accounts-password@1.4.0
|
||||
accounts-ui@1.1.9
|
||||
random@1.0.10
|
||||
dburles:collection-helpers
|
||||
@@ -18,33 +18,38 @@ dburles:mongo-collection-instances
|
||||
percolate:migrations
|
||||
ecwyne:mathjs
|
||||
useraccounts:polymer
|
||||
accounts-google@1.0.11
|
||||
accounts-google@1.2.0
|
||||
splendido:accounts-meld
|
||||
email@1.1.18
|
||||
email@1.2.3
|
||||
meteorhacks:subs-manager
|
||||
chuangbo:marked
|
||||
reywood:iron-router-ga
|
||||
meteor-base@1.0.4
|
||||
meteor-base@1.1.0
|
||||
mobile-experience@1.0.4
|
||||
mongo@1.1.14
|
||||
mongo@1.2.0
|
||||
blaze-html-templates
|
||||
session@1.1.7
|
||||
jquery@1.11.10
|
||||
tracker@1.1.1
|
||||
logging@1.1.16
|
||||
tracker@1.1.3
|
||||
logging@1.1.17
|
||||
reload@1.1.11
|
||||
ejson@1.0.13
|
||||
ejson@1.0.14
|
||||
spacebars
|
||||
check@1.2.4
|
||||
check@1.2.5
|
||||
useraccounts:iron-routing
|
||||
wizonesolutions:canonical
|
||||
standard-minifier-js@1.2.1
|
||||
shell-server@0.2.1
|
||||
standard-minifier-js@2.1.1
|
||||
shell-server@0.2.4
|
||||
seba:minifiers-autoprefixer
|
||||
nikogosovd:multiple-uihooks
|
||||
templates:array
|
||||
ecmascript@0.6.1
|
||||
ecmascript@0.8.2
|
||||
es5-shim@4.6.15
|
||||
differential:vulcanize
|
||||
reactive-dict
|
||||
reactive-dict@1.1.9
|
||||
percolate:synced-cron
|
||||
ongoworks:speakingurl
|
||||
service-configuration@1.0.11
|
||||
google-config-ui
|
||||
dynamic-import
|
||||
ddp-rate-limiter
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@1.4.2.6
|
||||
METEOR@1.5.2
|
||||
|
||||
@@ -1,53 +1,57 @@
|
||||
accounts-base@1.2.14
|
||||
accounts-google@1.0.11
|
||||
accounts-base@1.3.3
|
||||
accounts-google@1.2.0
|
||||
accounts-oauth@1.1.15
|
||||
accounts-password@1.3.3
|
||||
accounts-password@1.4.0
|
||||
accounts-ui@1.1.9
|
||||
accounts-ui-unstyled@1.1.13
|
||||
accounts-ui-unstyled@1.2.1
|
||||
aldeed:collection2@2.10.0
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:schema-deny@1.1.0
|
||||
aldeed:schema-index@1.1.1
|
||||
aldeed:simple-schema@1.5.3
|
||||
allow-deny@1.0.5
|
||||
allow-deny@1.0.9
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.13.0
|
||||
babel-compiler@6.20.0
|
||||
babel-runtime@1.0.1
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
blaze@2.3.0
|
||||
blaze-html-templates@1.1.0
|
||||
blaze@2.3.2
|
||||
blaze-html-templates@1.1.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.0.11
|
||||
boilerplate-generator@1.2.0
|
||||
caching-compiler@1.1.9
|
||||
caching-html-compiler@1.1.0
|
||||
caching-html-compiler@1.1.2
|
||||
callback-hook@1.0.10
|
||||
check@1.2.4
|
||||
check@1.2.5
|
||||
chuangbo:marked@0.3.5_1
|
||||
coffeescript@1.11.1_4
|
||||
dburles:collection-helpers@1.1.0
|
||||
dburles:mongo-collection-instances@0.3.5
|
||||
ddp@1.2.5
|
||||
ddp-client@1.3.2
|
||||
ddp-common@1.2.8
|
||||
ddp-rate-limiter@1.0.6
|
||||
ddp-server@1.3.12
|
||||
ddp@1.3.1
|
||||
ddp-client@2.1.3
|
||||
ddp-common@1.2.9
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@2.0.2
|
||||
deps@1.0.12
|
||||
diff-sequence@1.0.7
|
||||
differential:vulcanize@3.0.0
|
||||
ecmascript@0.6.1
|
||||
ecmascript-runtime@0.3.15
|
||||
dynamic-import@0.1.3
|
||||
ecmascript@0.8.2
|
||||
ecmascript-runtime@0.4.1
|
||||
ecmascript-runtime-client@0.4.3
|
||||
ecmascript-runtime-server@0.4.1
|
||||
ecwyne:mathjs@0.25.0
|
||||
ejson@1.0.13
|
||||
email@1.1.18
|
||||
ejson@1.0.14
|
||||
email@1.2.3
|
||||
es5-shim@4.6.15
|
||||
fastclick@1.0.13
|
||||
geojson-utils@1.0.10
|
||||
google@1.1.15
|
||||
google-config-ui@1.0.0
|
||||
google-oauth@1.2.4
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.2.10
|
||||
http@1.2.12
|
||||
id-map@1.0.9
|
||||
iron:controller@1.0.12
|
||||
iron:core@1.0.11
|
||||
@@ -55,44 +59,46 @@ iron:dynamic-template@1.0.12
|
||||
iron:layout@1.0.12
|
||||
iron:location@1.0.11
|
||||
iron:middleware-stack@1.1.0
|
||||
iron:router@1.1.1
|
||||
iron:url@1.0.11
|
||||
iron:router@1.1.2
|
||||
iron:url@1.1.0
|
||||
jquery@1.11.10
|
||||
lai:collection-extensions@0.2.1_1
|
||||
launch-screen@1.1.0
|
||||
launch-screen@1.1.1
|
||||
less@2.7.9
|
||||
livedata@1.0.18
|
||||
localstorage@1.0.12
|
||||
logging@1.1.16
|
||||
localstorage@1.1.1
|
||||
logging@1.1.17
|
||||
matb33:collection-hooks@0.8.4
|
||||
mdg:validation-error@0.5.1
|
||||
meteor@1.6.0
|
||||
meteor-base@1.0.4
|
||||
meteor@1.7.2
|
||||
meteor-base@1.1.0
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
minifier-css@1.2.16
|
||||
minifier-js@1.2.17
|
||||
minimongo@1.0.19
|
||||
minifier-js@2.1.3
|
||||
minimongo@1.3.1
|
||||
mobile-experience@1.0.4
|
||||
mobile-status-bar@1.0.13
|
||||
modules@0.7.7
|
||||
modules-runtime@0.7.8
|
||||
momentjs:moment@2.17.1
|
||||
mongo@1.1.14
|
||||
mobile-status-bar@1.0.14
|
||||
modules@0.10.0
|
||||
modules-runtime@0.8.0
|
||||
momentjs:moment@2.18.1
|
||||
mongo@1.2.2
|
||||
mongo-dev-server@1.0.1
|
||||
mongo-id@1.0.6
|
||||
nikogosovd:multiple-uihooks@0.1.8
|
||||
npm-bcrypt@0.9.2
|
||||
npm-mongo@2.2.16_1
|
||||
oauth@1.1.12
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@2.2.30
|
||||
oauth@1.1.13
|
||||
oauth2@1.1.11
|
||||
observe-sequence@1.0.14
|
||||
observe-sequence@1.0.16
|
||||
ongoworks:speakingurl@9.0.0
|
||||
ordered-dict@1.0.9
|
||||
percolate:migrations@0.9.8
|
||||
percolate:synced-cron@1.3.2
|
||||
promise@0.8.8
|
||||
promise@0.9.0
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.0.10
|
||||
rate-limit@1.0.6
|
||||
reactive-dict@1.1.8
|
||||
rate-limit@1.0.8
|
||||
reactive-dict@1.1.9
|
||||
reactive-var@1.0.11
|
||||
reload@1.1.11
|
||||
retry@1.0.9
|
||||
@@ -102,27 +108,27 @@ seba:minifiers-autoprefixer@1.0.1
|
||||
service-configuration@1.0.11
|
||||
session@1.1.7
|
||||
sha@1.0.9
|
||||
shell-server@0.2.1
|
||||
softwarerero:accounts-t9n@1.3.7
|
||||
spacebars@1.0.13
|
||||
spacebars-compiler@1.1.0
|
||||
shell-server@0.2.4
|
||||
softwarerero:accounts-t9n@1.3.11
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.3
|
||||
splendido:accounts-emails-field@1.2.0
|
||||
splendido:accounts-meld@1.3.1
|
||||
srp@1.0.10
|
||||
standard-minifier-js@1.2.2
|
||||
standard-minifier-js@2.1.1
|
||||
templates:array@1.0.3
|
||||
templating@1.3.0
|
||||
templating-compiler@1.3.0
|
||||
templating-runtime@1.3.0
|
||||
templating-tools@1.1.0
|
||||
tracker@1.1.1
|
||||
ui@1.0.12
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.3
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
tracker@1.1.3
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.0.11
|
||||
url@1.1.0
|
||||
useraccounts:core@1.14.2
|
||||
useraccounts:iron-routing@1.14.2
|
||||
useraccounts:polymer@1.14.2
|
||||
webapp@1.3.12
|
||||
webapp@1.3.19
|
||||
webapp-hashing@1.0.9
|
||||
wizonesolutions:canonical@0.0.5
|
||||
zimme:collection-behaviours@1.1.3
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
Parties = new Mongo.Collection("parties");
|
||||
|
||||
Schemas.Party = new SimpleSchema({
|
||||
//each character/monster can only be in one party at a time
|
||||
//each party can only be in a single instance at a time
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: "New Party",
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
characters: {
|
||||
type: [String],
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
defaultValue: [],
|
||||
},
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
});
|
||||
|
||||
Parties.attachSchema(Schemas.Party);
|
||||
|
||||
Parties.allow({
|
||||
insert: function(userId, doc) {
|
||||
return userId && doc.owner === userId;
|
||||
},
|
||||
update: function(userId, doc, fields, modifier) {
|
||||
return userId && doc.owner === userId;
|
||||
},
|
||||
remove: function(userId, doc) {
|
||||
return userId && doc.owner === userId;
|
||||
},
|
||||
fetch: ["owner"],
|
||||
});
|
||||
|
||||
Parties.deny({
|
||||
update: function(userId, docs, fields, modifier) {
|
||||
// can't change owners
|
||||
return _.contains(fields, "owner");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,10 +11,12 @@ Schemas.Action = new SimpleSchema({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
type: {
|
||||
|
||||
@@ -12,6 +12,7 @@ Schemas.Attack = new SimpleSchema({
|
||||
name: {
|
||||
type: String,
|
||||
defaultValue: "New Attack",
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
details: {
|
||||
|
||||
@@ -8,6 +8,7 @@ Schemas.Buff = new SimpleSchema({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
description: {
|
||||
@@ -22,7 +23,7 @@ Schemas.Buff = new SimpleSchema({
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
"inate",
|
||||
"inate", //this should be "innate", but changing it could be problematic
|
||||
"custom",
|
||||
],
|
||||
},
|
||||
@@ -41,12 +42,26 @@ Schemas.Buff = new SimpleSchema({
|
||||
allowedValues: _.pluck(colorOptions, "key"),
|
||||
defaultValue: "q",
|
||||
},
|
||||
appliedBy: { //the charId of whoever applied the buff
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
appliedByDetails: {//the name and collection of the thing that applied the buff
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
"appliedByDetails.name": {
|
||||
type: String,
|
||||
},
|
||||
"appliedByDetails.collection": {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
Buffs.attachSchema(Schemas.Buff);
|
||||
|
||||
Buffs.attachBehaviour("softRemovable");
|
||||
makeParent(Buffs, ["name", "enabled"]); //parents of effects
|
||||
makeParent(Buffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies
|
||||
|
||||
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
@@ -4,6 +4,7 @@ Characters = new Mongo.Collection("characters");
|
||||
Schemas.Character = new SimpleSchema({
|
||||
//strings
|
||||
name: {type: String, defaultValue: "", trim: false, optional: true},
|
||||
urlName: {type: String, defaultValue: "-", trim: false, optional: true},
|
||||
alignment: {type: String, defaultValue: "", trim: false, optional: true},
|
||||
gender: {type: String, defaultValue: "", trim: false, optional: true},
|
||||
race: {type: String, defaultValue: "", trim: false, optional: true},
|
||||
@@ -26,6 +27,7 @@ Schemas.Character = new SimpleSchema({
|
||||
|
||||
//stats
|
||||
hitPoints: {type: Schemas.Attribute},
|
||||
tempHP: {type: Schemas.Attribute},
|
||||
experience: {type: Schemas.Attribute},
|
||||
proficiencyBonus: {type: Schemas.Attribute},
|
||||
speed: {type: Schemas.Attribute},
|
||||
@@ -162,9 +164,9 @@ Schemas.Character = new SimpleSchema({
|
||||
|
||||
//permissions
|
||||
party: {type: String, regEx: SimpleSchema.RegEx.Id, optional: true},
|
||||
owner: {type: String, regEx: SimpleSchema.RegEx.Id},
|
||||
readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
|
||||
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: []},
|
||||
owner: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
readers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
|
||||
writers: {type: [String], regEx: SimpleSchema.RegEx.Id, defaultValue: [], index: 1},
|
||||
color: {
|
||||
type: String,
|
||||
allowedValues: _.pluck(colorOptions, "key"),
|
||||
@@ -183,7 +185,13 @@ Schemas.Character = new SimpleSchema({
|
||||
type: String,
|
||||
defaultValue: "whitelist",
|
||||
allowedValues: ["whitelist", "public"],
|
||||
index: 1,
|
||||
},
|
||||
"settings.swapStatAndModifier": {type: Boolean, defaultValue: false},
|
||||
"settings.exportFeatures": {type: Boolean, defaultValue: true},
|
||||
"settings.exportAttacks": {type: Boolean, defaultValue: true},
|
||||
"settings.exportDescription": {type: Boolean, defaultValue: true},
|
||||
"settings.newUserExperience": {type: Boolean, optional: true},
|
||||
});
|
||||
|
||||
Characters.attachSchema(Schemas.Character);
|
||||
@@ -254,7 +262,10 @@ var attributeBase = preventLoop(function(charId, statName){
|
||||
var result = (base + add) * mul;
|
||||
if (result < min) result = min;
|
||||
if (result > max) result = max;
|
||||
|
||||
// Don't round carry multiplier
|
||||
if (statName === "carryMultiplier"){
|
||||
return result;
|
||||
}
|
||||
return Math.floor(result);
|
||||
});
|
||||
|
||||
@@ -274,6 +285,7 @@ if (Meteor.isClient) {
|
||||
|
||||
//create a local memoize with a argument concatenating hash function
|
||||
var memoize = function(f) {
|
||||
if (Meteor.isServer) return f;
|
||||
return Tracker.memoize(f, function() {
|
||||
return _.reduce(arguments, function(memo, arg) {
|
||||
return memo + arg;
|
||||
@@ -287,6 +299,7 @@ Characters.calculate = {
|
||||
var fieldSelector = {};
|
||||
fieldSelector[fieldName] = 1;
|
||||
var char = Characters.findOne(charId, {fields: fieldSelector});
|
||||
if (!char) return;
|
||||
var field = char[fieldName];
|
||||
if (field === undefined){
|
||||
throw new Meteor.Error(
|
||||
@@ -320,6 +333,7 @@ Characters.calculate = {
|
||||
},
|
||||
attributeValue: memoize(function(charId, attributeName){
|
||||
var attribute = Characters.calculate.getField(charId, attributeName);
|
||||
if (!attribute) return;
|
||||
//base value
|
||||
var value = Characters.calculate.attributeBase(charId, attributeName);
|
||||
//plus adjustment
|
||||
@@ -331,6 +345,7 @@ Characters.calculate = {
|
||||
}),
|
||||
skillMod: memoize(preventLoop(function(charId, skillName){
|
||||
var skill = Characters.calculate.getField(charId, skillName);
|
||||
if (!skill) return;
|
||||
//get the final value of the ability score
|
||||
var ability = Characters.calculate.attributeValue(charId, skill.ability);
|
||||
|
||||
@@ -382,7 +397,6 @@ Characters.calculate = {
|
||||
return prof && prof.value || 0;
|
||||
}),
|
||||
passiveSkill: memoize(function(charId, skillName){
|
||||
var skill = Characters.calculate.getField(charId, skillName);
|
||||
var mod = +Characters.calculate.skillMod(charId, skillName);
|
||||
var value = 10 + mod;
|
||||
Effects.find(
|
||||
@@ -525,6 +539,7 @@ if (Meteor.isServer){
|
||||
Attacks .remove({charId: character._id});
|
||||
Buffs .remove({charId: character._id});
|
||||
Classes .remove({charId: character._id});
|
||||
CustomBuffs .remove({charId: character._id});
|
||||
Effects .remove({charId: character._id});
|
||||
Experiences .remove({charId: character._id});
|
||||
Features .remove({charId: character._id});
|
||||
@@ -534,6 +549,19 @@ if (Meteor.isServer){
|
||||
Items .remove({charId: character._id});
|
||||
Containers .remove({charId: character._id});
|
||||
});
|
||||
Characters.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
if (_.contains(fieldNames, "name")){
|
||||
var urlName = getSlug(doc.name, {maintainCase: true}) || "-";
|
||||
Characters.update(doc._id, {$set: {urlName}});
|
||||
}
|
||||
});
|
||||
Characters.before.insert(function(userId, doc) {
|
||||
doc.urlName = getSlug(doc.name, {maintainCase: true}) || "-";
|
||||
// The first character a user creates should have the new user experience
|
||||
if (!Characters.find({owner: userId}).count()){
|
||||
doc.settings.newUserExperience = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Characters.allow({
|
||||
|
||||
@@ -2,7 +2,7 @@ Classes = new Mongo.Collection("classes");
|
||||
|
||||
Schemas.Class = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
name: {type: String, trim: false},
|
||||
name: {type: String, optional: true, trim: false},
|
||||
level: {type: Number},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
|
||||
42
rpg-docs/Model/Character/Conditions.js
Normal file
42
rpg-docs/Model/Character/Conditions.js
Normal file
@@ -0,0 +1,42 @@
|
||||
Conditions = new Mongo.Collection("conditions");
|
||||
|
||||
Schemas.Conditions = new SimpleSchema({
|
||||
charId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
"lifeTime.total": {
|
||||
type: Number,
|
||||
defaultValue: 0, //0 is infinite
|
||||
min: 0,
|
||||
},
|
||||
"lifeTime.spent": {
|
||||
type: Number,
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
allowedValues: _.pluck(colorOptions, "key"),
|
||||
defaultValue: "q",
|
||||
},
|
||||
});
|
||||
|
||||
Conditions.attachSchema(Schemas.Conditions);
|
||||
|
||||
Conditions.attachBehaviour("softRemovable");
|
||||
makeParent(Conditions, ["name"]); //parents of effects, attacks, proficiencies
|
||||
|
||||
Conditions.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Conditions.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
53
rpg-docs/Model/Character/CustomBuffs.js
Normal file
53
rpg-docs/Model/Character/CustomBuffs.js
Normal file
@@ -0,0 +1,53 @@
|
||||
CustomBuffs = new Mongo.Collection("customBuffs");
|
||||
|
||||
Schemas.CustomBuff = new SimpleSchema({
|
||||
charId: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
index: 1,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
"self",
|
||||
"others",
|
||||
"both"
|
||||
],
|
||||
defaultValue: "self",
|
||||
},
|
||||
enabled: {
|
||||
type: Boolean,
|
||||
autoValue: function(){
|
||||
return false;
|
||||
//enabled is ALWAYS false on these, so that its children are also not enabled, so that the buff templates have no effects.
|
||||
},
|
||||
},
|
||||
"lifeTime.total": {
|
||||
type: Number,
|
||||
defaultValue: 0, //0 is infinite
|
||||
min: 0,
|
||||
},
|
||||
//the id of the feature, buff or item that creates this buff
|
||||
parent: {
|
||||
type: Schemas.Parent,
|
||||
},
|
||||
});
|
||||
|
||||
CustomBuffs.attachSchema(Schemas.CustomBuff);
|
||||
|
||||
CustomBuffs.attachBehaviour("softRemovable");
|
||||
makeParent(CustomBuffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies. Since this represents a template, "enabled" is always false.
|
||||
makeChild(CustomBuffs); //children of lots of things
|
||||
|
||||
CustomBuffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
CustomBuffs.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
@@ -2,7 +2,7 @@ Experiences = new Mongo.Collection("experience");
|
||||
|
||||
Schemas.Experience = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
name: {type: String, defaultValue: "New Experience", trim: false},
|
||||
name: {type: String, optional: true, trim: false, defaultValue: "New Experience"},
|
||||
description: {type: String, optional: true, trim: false},
|
||||
value: {type: Number, defaultValue: 0},
|
||||
dateAdded: {
|
||||
|
||||
@@ -2,7 +2,7 @@ Features = new Mongo.Collection("features");
|
||||
|
||||
Schemas.Feature = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
name: {type: String, trim: false},
|
||||
name: {type: String, optional: true, trim: false},
|
||||
description: {type: String, optional: true, trim: false},
|
||||
uses: {type: String, optional: true, trim: false},
|
||||
used: {type: Number, defaultValue: 0},
|
||||
|
||||
@@ -2,7 +2,7 @@ Notes = new Mongo.Collection("notes");
|
||||
|
||||
Schemas.Note = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
name: {type: String, trim: false},
|
||||
name: {type: String, optional: true, trim: false},
|
||||
description: {type: String, optional: true, trim: false},
|
||||
color: {
|
||||
type: String,
|
||||
|
||||
@@ -2,7 +2,7 @@ SpellLists = new Mongo.Collection("spellLists");
|
||||
|
||||
Schemas.SpellLists = new SimpleSchema({
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
name: {type: String, trim: false},
|
||||
name: {type: String, optional: true, trim: false},
|
||||
description: {type: String, optional: true, trim: false},
|
||||
saveDC: {type: String, optional: true, trim: false},
|
||||
attackBonus: {type: String, optional: true, trim: false},
|
||||
|
||||
@@ -9,6 +9,7 @@ Schemas.Spell = new SimpleSchema({
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
trim: false,
|
||||
defaultValue: "New Spell",
|
||||
},
|
||||
@@ -82,3 +83,170 @@ Spells.after.update(function (userId, spell, fieldNames) {
|
||||
|
||||
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||
|
||||
|
||||
|
||||
|
||||
var checkMovePermission = function(spellId, parent, destinationOnly) {
|
||||
var spell = Spells.findOne(spellId);
|
||||
if (!spell)
|
||||
throw new Meteor.Error("No such spell",
|
||||
"An spell could not be found to move");
|
||||
//handle permissions
|
||||
var permission;
|
||||
|
||||
if (!destinationOnly) { //if we're not modifying the origin, only the destination
|
||||
permission = Meteor.call("canWriteCharacter", spell.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move spells from this character");
|
||||
}
|
||||
}
|
||||
if (parent.collection === "Characters"){
|
||||
permission = Meteor.call("canWriteCharacter", parent.id);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move spells to this character");
|
||||
}
|
||||
} else {
|
||||
var parentCollectionObject = global[parent.collection];
|
||||
var parentObject = null;
|
||||
if (parentCollectionObject)
|
||||
parentObject = parentCollectionObject.findOne(
|
||||
parent.id, {fields: {_id: 1, charId: 1}}
|
||||
);
|
||||
if (!parentObject) throw new Meteor.Error(
|
||||
"Invalid parent",
|
||||
"The destination parent " + parent.id +
|
||||
" does not exist in the collection " + parent.collection
|
||||
);
|
||||
if (parentObject.charId){
|
||||
permission = Meteor.call("canWriteCharacter", parentObject.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move spells to this character");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var moveSpell = function(spellId, targetCollection, targetId) {
|
||||
var spell = Spells.findOne(spellId);
|
||||
if (!spell) return;
|
||||
targetCollection = targetCollection || spell.parent.collection;
|
||||
targetId = targetId || spell.parent.id;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, false);
|
||||
}
|
||||
|
||||
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
|
||||
targetList = SpellLists.findOne({"charId": targetId});
|
||||
targetListId = targetList && targetList._id;
|
||||
if (!targetListId) {
|
||||
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
|
||||
name: "New SpellList",
|
||||
charId: targetId,
|
||||
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||
});
|
||||
}
|
||||
|
||||
Spells.update(
|
||||
spellId,
|
||||
{$set: {
|
||||
charId: targetId,
|
||||
"parent.collection": "SpellLists",
|
||||
"parent.id": targetListId,
|
||||
}}
|
||||
);
|
||||
}
|
||||
else { //we are moving the spell within the same character
|
||||
//update the spell provided the update will actually change something
|
||||
if (
|
||||
spell.parent.collection !== targetCollection ||
|
||||
spell.parent.id !== targetId
|
||||
){
|
||||
Spells.update(
|
||||
spellId,
|
||||
{$set: {
|
||||
"parent.collection": targetCollection,
|
||||
"parent.id": targetId,
|
||||
}}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var copySpell = function(spellId, targetCollection, targetId) {
|
||||
var spell = Spells.findOne(spellId);
|
||||
if (!spell) return;
|
||||
targetCollection = targetCollection || spell.parent.collection;
|
||||
targetId = targetId || spell.parent.id;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, true); //we're only reading from the source character
|
||||
}
|
||||
|
||||
|
||||
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
|
||||
targetList = SpellLists.findOne({"charId": targetId});
|
||||
targetListId = targetList && targetList._id;
|
||||
if (!targetListId) {
|
||||
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
|
||||
name: "New SpellList",
|
||||
charId: targetId,
|
||||
saveDC: "8 + intelligenceMod + proficiencyBonus",
|
||||
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||
});
|
||||
}
|
||||
|
||||
newSpell = _.clone(spell);
|
||||
delete newSpell._id;
|
||||
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
|
||||
Spells.update(
|
||||
newSpellId,
|
||||
{$set: {
|
||||
charId: targetId,
|
||||
"parent.collection": "SpellLists",
|
||||
"parent.id": targetListId,
|
||||
}}
|
||||
);
|
||||
}
|
||||
else { //else we are copying the spell within the same character
|
||||
newSpell = _.clone(spell);
|
||||
delete newSpell._id;
|
||||
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
|
||||
Spells.update(
|
||||
newSpellId,
|
||||
{$set: {
|
||||
"parent.collection": targetCollection,
|
||||
"parent.id": targetId,
|
||||
}}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Meteor.methods({
|
||||
moveSpellToList: function(spellId, spellListId) {
|
||||
check(spellId, String);
|
||||
check(spellListId, String);
|
||||
moveSpell(spellId, "SpellLists", spellListId);
|
||||
},
|
||||
copySpellToList: function(spellId, spellListId) {
|
||||
check(spellId, String);
|
||||
check(spellListId, String);
|
||||
copySpell(spellId, "SpellLists", spellListId);
|
||||
},
|
||||
moveSpellToCharacter: function(spellId, charId) {
|
||||
check(spellId, String);
|
||||
check(charId, String);
|
||||
moveSpell(spellId, "Characters", charId);
|
||||
},
|
||||
copySpellToCharacter: function(spellId, charId) {
|
||||
check(spellId, String);
|
||||
check(charId, String);
|
||||
copySpell(spellId, "Characters", charId);
|
||||
},
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
Containers = new Mongo.Collection("containers");
|
||||
|
||||
Schemas.Container = new SimpleSchema({
|
||||
name: {type: String, trim: false},
|
||||
name: {type: String, optional: true, trim: false},
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||
isCarried: {type: Boolean},
|
||||
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Items = new Mongo.Collection("items");
|
||||
|
||||
Schemas.Item = new SimpleSchema({
|
||||
name: {type: String, defaultValue: "New Item", trim: false},
|
||||
name: {type: String, optional: true, trim: false, defaultValue: "New Item"},
|
||||
plural: {type: String, optional: true, trim: false},
|
||||
description:{type: String, optional: true, trim: false},
|
||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
|
||||
|
||||
@@ -11,13 +11,11 @@ Schemas.LibraryAttacks = new SimpleSchema({
|
||||
},
|
||||
attackBonus: {
|
||||
type: String,
|
||||
defaultValue: "strengthMod + proficiencyBonus",
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
damage: {
|
||||
type: String,
|
||||
defaultValue: "1d8 + {strengthMod}",
|
||||
optional: true,
|
||||
trim: false,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
LibraryItems = new Mongo.Collection("libraryItems");
|
||||
|
||||
Schemas.LibraryItems = new SimpleSchema({
|
||||
libraryName:{type: String, optional: true, trim: false},
|
||||
name: {type: String, defaultValue: "New Item", trim: false},
|
||||
plural: {type: String, optional: true, trim: false},
|
||||
description:{type: String, optional: true, trim: false},
|
||||
|
||||
18
rpg-docs/Routes/API.js
Normal file
18
rpg-docs/Routes/API.js
Normal file
@@ -0,0 +1,18 @@
|
||||
Router.map(function() {
|
||||
this.route("vmixCharacter", {
|
||||
path: "/vmix-character/:_id/",
|
||||
where: "server",
|
||||
action: function() {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
this.response.end(vMixCharacter(this.params._id));
|
||||
},
|
||||
});
|
||||
this.route("vmixParty", {
|
||||
path: "/vmix-party/:_id/",
|
||||
where: "server",
|
||||
action: function() {
|
||||
this.response.setHeader("Content-Type", "application/json");
|
||||
this.response.end(vMixParty(this.params._id));
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -24,7 +24,7 @@ Router.map(function() {
|
||||
this.route("characterList", {
|
||||
path: "/characterList",
|
||||
waitOn: function(){
|
||||
return subsManager.subscribe("characterList", Meteor.userId());
|
||||
return subsManager.subscribe("characterList");
|
||||
},
|
||||
onAfterAction: function() {
|
||||
document.title = appName + " - Characters";
|
||||
@@ -32,11 +32,27 @@ Router.map(function() {
|
||||
fastRender: true,
|
||||
});
|
||||
|
||||
this.route("characterSheet", {
|
||||
path: "/character/:_id",
|
||||
this.route("characterSheetNaked", {
|
||||
path: "/character/:_id/",
|
||||
waitOn: function(){
|
||||
return [
|
||||
subsManager.subscribe("singleCharacter", this.params._id, Meteor.userId()),
|
||||
subsManager.subscribe("singleCharacter", this.params._id),
|
||||
];
|
||||
},
|
||||
action: function(){
|
||||
var _id = this.params._id
|
||||
var character = Characters.findOne(_id);
|
||||
var urlName = character && character.urlName;
|
||||
var path = `\/character\/${_id}\/${urlName || "-"}`;
|
||||
Router.go(path,{},{replaceState:true});
|
||||
},
|
||||
});
|
||||
|
||||
this.route("characterSheet", {
|
||||
path: "/character/:_id/:urlName",
|
||||
waitOn: function(){
|
||||
return [
|
||||
subsManager.subscribe("singleCharacter", this.params._id),
|
||||
];
|
||||
},
|
||||
data: function() {
|
||||
|
||||
@@ -3,7 +3,8 @@ Template.registerHelper("canEditCharacter", function(charId) {
|
||||
});
|
||||
|
||||
canEditCharacter = function(charId) {
|
||||
var char = Characters.findOne(charId)
|
||||
var char = Characters.findOne(charId);
|
||||
if (!char) return false;
|
||||
var userId = Meteor.userId();
|
||||
return char.owner === userId ||
|
||||
_.contains(char.writers, userId);
|
||||
|
||||
3
rpg-docs/client/globalHelpers/characterPath.js
Normal file
3
rpg-docs/client/globalHelpers/characterPath.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Template.registerHelper("characterPath", function(char) {
|
||||
return `\/character\/${char._id}\/${char.urlName || "-"}`;
|
||||
});
|
||||
@@ -24,6 +24,10 @@ Template.registerHelper("evaluateString", function(charId, string) {
|
||||
return evaluateString(charId, string);
|
||||
});
|
||||
|
||||
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
|
||||
return evaluateSpellString(charId, spellListId, string);
|
||||
});
|
||||
|
||||
Template.registerHelper("evaluateShortString", function(charId, string) {
|
||||
if (_.isString(string)){
|
||||
return evaluateString(
|
||||
|
||||
@@ -18,6 +18,9 @@ openParentDialog = function({
|
||||
} else if (parent.collection === "Spells") {
|
||||
template = "spellDialog";
|
||||
data = {spellId: parent.id};
|
||||
} else if (parent.collection === "Buffs") {
|
||||
template = "buffDialog";
|
||||
data = {buffId: parent.id};
|
||||
}
|
||||
pushDialogStack({template, data, element, returnElement, callback});
|
||||
};
|
||||
|
||||
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
@@ -0,0 +1,195 @@
|
||||
improvedInitiativeJson = function(charId, options){
|
||||
options = options || {
|
||||
features: true,
|
||||
attacks: true,
|
||||
description: true,
|
||||
};
|
||||
var char = Characters.findOne(charId);
|
||||
if (!char) return;
|
||||
var baseValue = function(attributeName){
|
||||
return Characters.calculate.attributeBase(charId, attributeName);
|
||||
};
|
||||
var skillMod = function(skillName){
|
||||
return Characters.calculate.skillMod(charId, skillName);
|
||||
};
|
||||
var damageMods = getDamageMods(charId);
|
||||
return JSON.stringify({
|
||||
"Id": char._id,
|
||||
"Name": char.name,
|
||||
"Source": "DiceCloud",
|
||||
"Type": char.race,
|
||||
"HP": {
|
||||
"Value": baseValue("hitPoints"),
|
||||
"Notes": getHitDiceString(charId) || "",
|
||||
},
|
||||
"AC": {
|
||||
"Value": baseValue("armor"),
|
||||
"Notes": getArmorString(charId) || "",
|
||||
},
|
||||
"InitiativeModifier": skillMod("initiative"),
|
||||
"Speed": ["" + baseValue("speed")],
|
||||
"Abilities": {
|
||||
"Str": baseValue("strength"),
|
||||
"Dex": baseValue("dexterity"),
|
||||
"Con": baseValue("constitution"),
|
||||
"Cha": baseValue("charisma"),
|
||||
"Int": baseValue("intelligence"),
|
||||
"Wis": baseValue("wisdom"),
|
||||
},
|
||||
"DamageVulnerabilities": damageMods.vulnerabilities,
|
||||
"DamageResistances": damageMods.resistances,
|
||||
"DamageImmunities": damageMods.immunities,
|
||||
"ConditionImmunities": [],
|
||||
"Saves": [
|
||||
{"Name": "Str", "Modifier": skillMod("strengthSave")},
|
||||
{"Name": "Dex", "Modifier": skillMod("dexteritySave")},
|
||||
{"Name": "Con", "Modifier": skillMod("constitutionSave")},
|
||||
{"Name": "Int", "Modifier": skillMod("intelligenceSave")},
|
||||
{"Name": "Wis", "Modifier": skillMod("wisdomSave")},
|
||||
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
|
||||
],
|
||||
"Skills": getSkills(charId),
|
||||
"Senses": [
|
||||
"passive Perception " +
|
||||
Characters.calculate.passiveSkill(charId, "perception")
|
||||
],
|
||||
"Languages": getLanguages(charId),
|
||||
"Challenge": "",
|
||||
"Traits": options.features ? getTraits(charId) : [],
|
||||
"Actions": options.attacks ? getActions(charId) : [],
|
||||
"Reactions": [],
|
||||
"LegendaryActions": [],
|
||||
"Description": options.description ? char.description : "",
|
||||
"Player": Meteor.user().username,
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
var getHitDiceString = function(charId){
|
||||
var d6 = Characters.calculate.attributeBase(charId, "d6HitDice");
|
||||
var d8 = Characters.calculate.attributeBase(charId, "d8HitDice");
|
||||
var d10 = Characters.calculate.attributeBase(charId, "d10HitDice");
|
||||
var d12 = Characters.calculate.attributeBase(charId, "d12HitDice");
|
||||
var con = Characters.calculate.abilityMod(charId,"constitution");
|
||||
var string = "" +
|
||||
(d6 ? `${d6}d6 + ` : "") +
|
||||
(d8 ? `${d8}d8 + ` : "") +
|
||||
(d10 ? `${d10}d10 + ` : "") +
|
||||
(d12 ? `${d12}d12 + ` : "") +
|
||||
con;
|
||||
}
|
||||
|
||||
var getArmorString = function(charId){
|
||||
var bases = Effects.find({
|
||||
charId: charId,
|
||||
stat: "armor",
|
||||
operation: "base",
|
||||
enabled: true,
|
||||
}).map(e => ({
|
||||
ame: e.name,
|
||||
value: evaluateEffect(charId, e),
|
||||
}));
|
||||
var base = bases.length && _.max(bases, b => b.value).name || "";
|
||||
var effects = Effects.find({
|
||||
charId: charId,
|
||||
stat: "armor",
|
||||
operation: {$ne: "base"},
|
||||
enabled: true,
|
||||
}).map(e => e.name);
|
||||
var strings = base ? [base] : [];
|
||||
strings = strings.concat(effects);
|
||||
return strings.join(", ");
|
||||
}
|
||||
|
||||
var getDamageMods = function(charId){
|
||||
// jscs:disable maximumLineLength
|
||||
var multipliers = [
|
||||
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
|
||||
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
|
||||
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
|
||||
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
|
||||
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
|
||||
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
|
||||
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
|
||||
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
|
||||
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
|
||||
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
|
||||
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
|
||||
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
|
||||
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
|
||||
];
|
||||
// jscs:enable maximumLineLength
|
||||
multipliers = _.groupBy(multipliers, "value");
|
||||
var names = o => o.name;
|
||||
return {
|
||||
"immunities": _.map(multipliers["0"], names),
|
||||
"resistances": _.map(multipliers["0.5"], names),
|
||||
"vulnerabilities": _.map(multipliers["2"], names),
|
||||
};
|
||||
}
|
||||
|
||||
var getSkills = function(charId){
|
||||
var allSkills = [
|
||||
{name: "acrobatics", attribute: "dexterity"},
|
||||
{name: "animalHandling", attribute: "wisdom"},
|
||||
{name: "arcana", attribute: "intelligence"},
|
||||
{name: "athletics", attribute: "strength"},
|
||||
{name: "deception", attribute: "charisma"},
|
||||
{name: "history", attribute: "intelligence"},
|
||||
{name: "insight", attribute: "wisdom"},
|
||||
{name: "intimidation", attribute: "charisma"},
|
||||
{name: "investigation", attribute: "intelligence"},
|
||||
{name: "medicine", attribute: "wisdom"},
|
||||
{name: "nature", attribute: "intelligence"},
|
||||
{name: "perception", attribute: "wisdom"},
|
||||
{name: "performance", attribute: "charisma"},
|
||||
{name: "persuasion", attribute: "charisma"},
|
||||
{name: "religion", attribute: "intelligence"},
|
||||
{name: "sleightOfHand", attribute: "dexterity"},
|
||||
{name: "stealth", attribute: "dexterity"},
|
||||
{name: "survival", attribute: "wisdom"},
|
||||
];
|
||||
var skills = [];
|
||||
_.each(allSkills, skill => {
|
||||
var value = Characters.calculate.skillMod(charId, skill.name);
|
||||
var mod = Characters.calculate.abilityMod(charId, skill.attribute);
|
||||
if (value !== mod){
|
||||
skills.push({Name: skill.name, Modifier: value});
|
||||
}
|
||||
});
|
||||
return skills;
|
||||
};
|
||||
|
||||
var getLanguages = function(charId){
|
||||
return Proficiencies.find({
|
||||
charId,
|
||||
enabled: true,
|
||||
type: "language",
|
||||
}).map(l => l.name);
|
||||
};
|
||||
|
||||
var getTraits = function(charId){
|
||||
return Features.find(
|
||||
{charId: charId},
|
||||
{sort: {color: 1, name: 1}}
|
||||
).map(f => ({
|
||||
Name: f.name,
|
||||
// evaluateShortString helper
|
||||
Content: evaluateString(
|
||||
charId, f.description && f.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
|
||||
) || "",
|
||||
Usage: "",
|
||||
}));
|
||||
}
|
||||
|
||||
var getActions = function(charId){
|
||||
return Attacks.find(
|
||||
{charId, enabled: true},
|
||||
{sort: {color: 1, name: 1}}
|
||||
).map(a => ({
|
||||
Name: a.name,
|
||||
Content: `+${evaluate(charId, a.attackBonus)} to hit, ` +
|
||||
`${evaluateString(charId, a.damage)} ${a.damageType} damage, ` +
|
||||
`${a.details}`,
|
||||
Usage: "",
|
||||
}));
|
||||
}
|
||||
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
|
||||
|
||||
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
|
||||
|
||||
// MIT license
|
||||
var lastTime = 0;
|
||||
var vendors = ["ms", "moz", "webkit", "o"];
|
||||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"];
|
||||
window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] ||
|
||||
window[vendors[x] + "CancelRequestAnimationFrame"];
|
||||
}
|
||||
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function(callback, element) {
|
||||
var currTime = new Date().getTime();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||||
timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
17
rpg-docs/client/style/bounce.css
Normal file
17
rpg-docs/client/style/bounce.css
Normal file
@@ -0,0 +1,17 @@
|
||||
@keyframes bounce {
|
||||
from {
|
||||
transform: translate(0px,0px);
|
||||
}
|
||||
to {
|
||||
transform: translate(0px,-16px);
|
||||
}
|
||||
}
|
||||
|
||||
.bounce{
|
||||
animation-name: bounce;
|
||||
animation-duration: 0.3s;
|
||||
animation-direction: alternate;
|
||||
animation-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
animation-delay: 0s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 2px;
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.card .top {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
body .paper-font-display4, body .paper-font-display3, body .paper-font-title, body .paper-font-caption{
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.white-text {
|
||||
color: #dedede;
|
||||
color: rgba(255,255,255,0.87);
|
||||
|
||||
@@ -7,12 +7,32 @@ Template.attackEditList.helpers({
|
||||
|
||||
Template.attackEditList.events({
|
||||
"tap #addAttackButton": function() {
|
||||
if (typeof this.isSpell !== 'undefined' && this.isSpell) {
|
||||
var parentSpell = Spells.findOne({"_id": this.parentId})
|
||||
if (parentSpell && parentSpell.parent.collection == "SpellLists") {
|
||||
var spellList = SpellLists.findOne({"_id":parentSpell.parent.id});
|
||||
if (spellList && spellList.attackBonus) {
|
||||
Attacks.insert({
|
||||
charId: this.charId,
|
||||
parent: {
|
||||
id: this.parentId,
|
||||
collection: this.parentCollection
|
||||
},
|
||||
attackBonus: "attackBonus",
|
||||
damage: "1d10",
|
||||
damageType: "fire",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Attacks.insert({
|
||||
charId: this.charId,
|
||||
parent: {
|
||||
id: this.parentId,
|
||||
collection: this.parentCollection
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template name="attackView">
|
||||
<div class="attackView layout horizontal">
|
||||
<div class="paper-font-headline layout horizontal center" style="margin-right: 16px;">
|
||||
{{evaluateSigned charId attackBonus}}
|
||||
{{evaluateAttackBonus charId attack}}
|
||||
</div>
|
||||
<div class="layout vertical">
|
||||
<div>
|
||||
{{evaluateString charId damage}} {{damageType}}
|
||||
{{evaluateDamage charId attack}} {{attack.damageType}}
|
||||
</div>
|
||||
{{#if details}}
|
||||
{{#if attack.details}}
|
||||
<div class="paper-font-caption">
|
||||
{{details}}
|
||||
{{attack.details}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
Template.attackView.helpers ({
|
||||
evaluateAttackBonus: function(charId, attack) {
|
||||
if (attack.parent.collection == "Spells") {
|
||||
var spell = Spells.findOne(attack.parent.id);
|
||||
if (spell) {
|
||||
bonus = evaluate(charId, attack.attackBonus, {"spellListId": spell.parent.id});
|
||||
}
|
||||
} else {
|
||||
var bonus = evaluate(charId, attack.attackBonus);
|
||||
}
|
||||
|
||||
if (_.isFinite(bonus)) {
|
||||
return bonus > 0 ? "+" + bonus : "" + bonus;
|
||||
} else {
|
||||
return bonus;
|
||||
}
|
||||
},
|
||||
evaluateDamage: function(charId, attack) {
|
||||
if (attack.parent.collection == "Spells") {
|
||||
var spell = Spells.findOne(attack.parent.id);
|
||||
if (spell) {
|
||||
return evaluateSpellString(charId, spell.parent.id, attack.damage);
|
||||
}
|
||||
} else {
|
||||
return evaluateString(charId, attack.damage);
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -3,8 +3,8 @@
|
||||
<hr style="margin: 16px 0 16px 0;">
|
||||
<div class="attacks">
|
||||
<div class="spaceAfter paper-font-title">Attacks</div>
|
||||
{{#each attacks}}
|
||||
{{> attackView}}
|
||||
{{#each attack in attacks}}
|
||||
{{> attackView attack=attack charId=charId}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<!-- data is the CustomBuff -->
|
||||
<template name="applyBuffDialog">
|
||||
<div class="fit layout vertical applyBuffDialog">
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar>
|
||||
Apply Buff
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="flex layout horizontal" style="height:100%">
|
||||
<div class="flex" style="margin-right: 16px; height: 100%; max-width: 240px; overflow-y: auto;">
|
||||
{{> characterPicker selfId=buff.charId includeSelf=canApplyToSelf writableOnly=true}}
|
||||
</div>
|
||||
<div class="flex buff-description" style="height: 100%; overflow-y: auto">
|
||||
<hr style="margin: 16px 0 16px 0;">
|
||||
{{#if buff.description}}
|
||||
<div>{{#markdown}}{{evaluateString buff.charId buff.description}}{{/markdown}}</div>
|
||||
<hr style="margin: 16px 0 16px 0;">
|
||||
{{/if}}
|
||||
{{> effectsViewList charId=buff.charId parentId=buff._id}}
|
||||
{{> proficiencyViewList charId=buff.charId parentId=buff._id}}
|
||||
{{> attacksViewList charId=buff.charId parentId=buff._id}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
<paper-button id="cancelButton">
|
||||
Cancel
|
||||
</paper-button>
|
||||
<paper-button id="applyButton" disabled={{cantApply}}>
|
||||
Apply
|
||||
</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
Template.applyBuffDialog.onCreated(function(){
|
||||
this.selectedTarget = new ReactiveVar("default");
|
||||
});
|
||||
|
||||
Template.applyBuffDialog.helpers({
|
||||
cantApply: function() {
|
||||
return this.buff.target === "others" && Template.instance().selectedTarget.get() === "default"; //this is the only case where we can't apply a buff
|
||||
},
|
||||
canApplyToSelf: function() {
|
||||
return this.buff.target !== "others"; //i.e. it is "self" or "both"
|
||||
},
|
||||
});
|
||||
|
||||
Template.applyBuffDialog.events({
|
||||
"iron-select .characterPicker": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
var value = detail.item.getAttribute("name");
|
||||
Template.instance().selectedTarget.set(value);
|
||||
},
|
||||
"click #applyButton": function(event, instance){
|
||||
var targetId = Template.instance().selectedTarget.get();
|
||||
if (targetId === "default") {
|
||||
if (this.buff.target === "others") return; //since we have "Select a character" selected
|
||||
targetId = this.buff.charId; //otherwise, the default is to target self
|
||||
}
|
||||
|
||||
popDialogStack(targetId);
|
||||
},
|
||||
"click #cancelButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
@@ -1,15 +1,23 @@
|
||||
<template name="buffDialog">
|
||||
{{#with buff}}
|
||||
{{#baseDialog title=name class=colorClass hideEdit=true}}
|
||||
{{#baseDialog title=name class="white" hideColor=true startEditing=true editOnly=true}}
|
||||
{{> buffDetails}}
|
||||
{{else}}
|
||||
{{> buffDetails}}
|
||||
{{/baseDialog}}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
<template name="buffDetails">
|
||||
<div>
|
||||
{{appliedBy}}
|
||||
</div>
|
||||
<hr style="margin: 16px 0 16px 0;">
|
||||
{{#if description}}
|
||||
<div class="pre-wrap">{{evaluateString charId description}}</div>
|
||||
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
|
||||
<hr style="margin: 16px 0 16px 0;">
|
||||
{{/if}}
|
||||
|
||||
{{> effectsViewList charId=charId parentId=_id}}
|
||||
{{> proficiencyViewList charId=charId parentId=_id}}
|
||||
{{> attacksViewList charId=charId parentId=_id}}
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
Template.buffDialog.onCreated(function(){
|
||||
var buff = Buffs.findOne(this.buffId);
|
||||
Meteor.subscribe("singleCharacterName", buff.charId); //so we can access the names of public characters
|
||||
});
|
||||
|
||||
Template.buffDialog.helpers({
|
||||
buff: function(){
|
||||
return Buffs.findOne(this.buffId);
|
||||
},
|
||||
});
|
||||
|
||||
Template.buffDialog.events({
|
||||
"click #deleteButton": function(event, instance){
|
||||
Buffs.softRemoveNode(instance.data.buffId);
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
|
||||
const typeDict = {
|
||||
"Features": "feature",
|
||||
"Items": "item",
|
||||
"Spells": "spell",
|
||||
}; //really, we should only need these three
|
||||
|
||||
Template.buffDetails.helpers({
|
||||
appliedBy: function() {
|
||||
if (this.type == "inate") {
|
||||
return "Innate.";
|
||||
} else {
|
||||
var myName = Characters.findOne(this.charId).name;
|
||||
var applierCharacter = Characters.findOne(this.appliedBy) || {name: "???"}
|
||||
// "???" indicates that either we do not have read access to the buff-giver, or that the buff-giver does not exist.
|
||||
|
||||
if (applierCharacter.name === myName) {
|
||||
var charName = "your "
|
||||
} else {
|
||||
if (applierCharacter.name && applierCharacter.name[applierCharacter.name.length - 1] === 's') {
|
||||
var charName = applierCharacter.name + "' ";
|
||||
} else {
|
||||
var charName = applierCharacter.name + "'s ";
|
||||
}
|
||||
}
|
||||
|
||||
var type = typeDict[this.appliedByDetails.collection] + " ";
|
||||
var applierThing = this.appliedByDetails.name;
|
||||
|
||||
return "Applied by " + charName + type + applierThing + ".";
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
<template name="buffListItem">
|
||||
<div class="item buffListItem layout horizontal center">
|
||||
<div class="flex">
|
||||
{{buff.name}}
|
||||
</div>
|
||||
|
||||
{{#if canEditCharacter buff.charId}}
|
||||
<paper-icon-button class="deleteButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
icon="delete">
|
||||
</paper-icon-button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
Template.buffListItem.helpers({
|
||||
name: function() {
|
||||
return this.buff.name
|
||||
}
|
||||
});
|
||||
|
||||
Template.buffListItem.events({
|
||||
"click .buffListItem": function(event){
|
||||
var buffId = this.buff._id;
|
||||
var charId = this.buff.charId;
|
||||
pushDialogStack({
|
||||
template: "buffDialog",
|
||||
data: {buffId: buffId, charId: charId},
|
||||
element: event.currentTarget,
|
||||
});
|
||||
},
|
||||
"tap .deleteButton": function(event){
|
||||
event.stopPropagation();
|
||||
Buffs.remove(this.buff._id);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
.condition-library-dialog .item.selected {
|
||||
background-color: #e4e4e4;
|
||||
}
|
||||
|
||||
.condition-library-dialog table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.condition-library-dialog .library-condition td, tr {
|
||||
position: relative;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<template name="conditionLibraryDialog">
|
||||
<div class="fit condition-library-dialog layout vertical">
|
||||
<app-toolbar class="app-grey white-text">
|
||||
<paper-icon-button id="backButton"
|
||||
icon="arrow-back">
|
||||
</paper-icon-button>
|
||||
<div main-title>Conditions</div>
|
||||
</app-toolbar>
|
||||
<div class="flex scroll-y">
|
||||
<div class="conditions" style="padding:8px">
|
||||
<table style="width: 100%">
|
||||
<tbody>
|
||||
{{#each condition in conditions}}
|
||||
{{>libraryCondition condition=condition selected=(isSelected condition)}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout horizontal end-justified">
|
||||
<paper-button class="cancelButton">Cancel</paper-button>
|
||||
<paper-button class="okButton">OK</paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="libraryCondition">
|
||||
<tr class="item library-condition {{#if selected}}selected{{/if}}">
|
||||
<td class="conditionName">
|
||||
{{conditionName condition}}
|
||||
<paper-ripple></paper-ripple>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -0,0 +1,166 @@
|
||||
Template.conditionLibraryDialog.onCreated(function(){
|
||||
this.selectedCondition = new ReactiveVar();
|
||||
});
|
||||
|
||||
Template.conditionLibraryDialog.helpers({
|
||||
conditions: function(){
|
||||
return Object.keys(LIBRARY_CONDITIONS)
|
||||
},
|
||||
isSelected(condition){
|
||||
const selected = Template.instance().selectedCondition.get();
|
||||
return selected && selected === condition;
|
||||
},
|
||||
});
|
||||
|
||||
Template.conditionLibraryDialog.events({
|
||||
"click .cancelButton": function(event, template){
|
||||
popDialogStack();
|
||||
},
|
||||
"click .okButton": function(event, template){
|
||||
popDialogStack(template.selectedCondition.get());
|
||||
},
|
||||
"click .library-condition": function(event, template){
|
||||
template.selectedCondition.set(this.condition);
|
||||
},
|
||||
"click #backButton": function(event, template){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
|
||||
Template.libraryCondition.helpers({
|
||||
conditionName: function(name){
|
||||
return LIBRARY_CONDITIONS[name].buff.name;
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
LIBRARY_CONDITIONS = {
|
||||
//Conditions
|
||||
blind: {
|
||||
buff: {
|
||||
name: "Blind",
|
||||
description: "A blinded creature can’t see and automatically fails any ability check that requires sight.\n\nAttack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage.",
|
||||
},
|
||||
},
|
||||
|
||||
deaf: {
|
||||
buff: {
|
||||
name: "Deaf",
|
||||
description: "A deafened creature can’t hear and automatically fails any ability check that requires hearing.",
|
||||
},
|
||||
},
|
||||
|
||||
frightened: {
|
||||
buff: {
|
||||
name: "Frightened",
|
||||
description: "A frightened creature has disadvantage on ability checks and attack rolls while the source of its fear is within line of sight.\n\nThe creature can’t willingly move closer to the source of its fear.",
|
||||
}
|
||||
},
|
||||
|
||||
grappled: {
|
||||
buff:{
|
||||
name: "Grappled",
|
||||
description: "A grappled creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.\n\nThe condition ends if the grappler is incapacitated.\n\nThe condition also ends if an effect removes the grappled creature from the reach of the grappler or grappling effect, such as when a creature is hurled away by the thunder wave spell.",
|
||||
},
|
||||
},
|
||||
|
||||
incapacitated: {
|
||||
buff: {
|
||||
name: "Incapacitated",
|
||||
description: "An incapacitated creature can’t take actions or reactions.",
|
||||
}
|
||||
},
|
||||
|
||||
invisible: {
|
||||
buff: {
|
||||
name: "Invisible",
|
||||
description: "An invisible creature is impossible to see without the aid of magic or a special sense. For the purpose of hiding, the creature is heavily obscured. The creature’s location can be detected by any noise it makes or any tracks it leaves.\n\nAttack rolls against the creature have disadvantage, and the creature’s attack rolls have advantage.",
|
||||
}
|
||||
},
|
||||
|
||||
paralyzed: {
|
||||
buff: {
|
||||
name: "Paralyzed",
|
||||
description: "A paralyzed creature is **incapacitated** and can’t move or speak.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
|
||||
},
|
||||
},
|
||||
|
||||
petrified: {
|
||||
buff: {
|
||||
name: "Petrified",
|
||||
description: "A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.\n\nA petrified creature is **incapacitated** and can’t move or speak, and is unaware of its surroundings.\n\nAttack rolls against the creature have advantage.\n\nThe creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
|
||||
},
|
||||
},
|
||||
|
||||
poisoned: {
|
||||
buff: {
|
||||
name: "Poisoned",
|
||||
description: "A poisoned creature has disadvantage on attack rolls and ability checks.",
|
||||
},
|
||||
},
|
||||
|
||||
prone: {
|
||||
buff: {
|
||||
name: "Prone",
|
||||
description: "A prone creature’s only movement option is to crawl, unless it stands up and thereby ends the condition.\n\nThe creature has disadvantage on attack rolls.\n\nAn attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the attack roll has disadvantage.",
|
||||
}
|
||||
},
|
||||
|
||||
restrained: {
|
||||
buff: {
|
||||
name: "Restrained",
|
||||
description: "A restrained creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.\n\nAttack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage.\n\nThe creature has disadvantage on Dexterity saving throws.",
|
||||
},
|
||||
},
|
||||
|
||||
stunned: {
|
||||
buff: {
|
||||
name: "Stunned",
|
||||
description: "A stunned creature is **incapacitated**, can’t move, and can speak only falteringly\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.",
|
||||
},
|
||||
},
|
||||
|
||||
unconscious: {
|
||||
buff: {
|
||||
name: "Unconscious",
|
||||
description: "An unconscious creature is **incapacitated**, can’t move or speak, and is unaware of its surroundings.\n\nThe creature drops whatever it’s holding and falls **prone**.\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
|
||||
},
|
||||
},
|
||||
|
||||
exhaustion1: {
|
||||
buff: {
|
||||
name: "Exhaustion - 1",
|
||||
description: "Disadvantage on ability checks\n\nFinishing a long rest reduces a creature’s exhaustion level by 1, provided that the creature has also ingested some food and drink.",
|
||||
},
|
||||
},
|
||||
exhaustion2: {
|
||||
buff: {
|
||||
name: "Exhaustion - 2",
|
||||
description: "Speed halved",
|
||||
},
|
||||
},
|
||||
exhaustion3: {
|
||||
buff: {
|
||||
name: "Exhaustion - 3",
|
||||
description: "Disadvantage on attack rolls and saving throws",
|
||||
},
|
||||
},
|
||||
exhaustion4: {
|
||||
buff: {
|
||||
name: "Exhaustion - 4",
|
||||
description: "Hit point maximum halved",
|
||||
},
|
||||
},
|
||||
exhaustion5: {
|
||||
buff: {
|
||||
name: "Exhaustion - 5",
|
||||
description: "Speed reduced to 0",
|
||||
},
|
||||
},
|
||||
exhaustion6: {
|
||||
buff: {
|
||||
name: "Exhaustion - 6",
|
||||
description: "You have died of exhaustion",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
<template name="conditionView">
|
||||
<div class="item conditionView layout horizontal center">
|
||||
<div class="flex">
|
||||
{{condition.name}}
|
||||
</div>
|
||||
|
||||
{{#if canEditCharacter condition.charId}}
|
||||
<paper-icon-button class="deleteButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
icon="delete">
|
||||
</paper-icon-button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,15 @@
|
||||
Template.conditionView.events({
|
||||
"click .conditionView": function(event){
|
||||
var condition = this.condition;
|
||||
var charId = Template.parentData()._id;
|
||||
pushDialogStack({
|
||||
template: "conditionViewDialog",
|
||||
data: {condition: condition},
|
||||
element: event.currentTarget,
|
||||
});
|
||||
},
|
||||
"tap .deleteButton": function(event){
|
||||
event.stopPropagation();
|
||||
Conditions.remove(this.condition._id);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
<template name="conditionViewDialog">
|
||||
{{#baseDialog title=condition.name class="white" hideColor=true startEditing=true editOnly=true}}}
|
||||
{{> conditionDetails condition=condition}}
|
||||
{{else}}
|
||||
{{> conditionDetails condition=condition}}
|
||||
{{/baseDialog}}
|
||||
</template>
|
||||
|
||||
<template name="conditionDetails">
|
||||
{{#if condition.description}}
|
||||
<div>{{#markdown}}{{evaluateString condition.charId condition.description}}{{/markdown}}</div>
|
||||
{{/if}}
|
||||
{{> effectsViewList charId=condition.charId parentId=condition._id}}
|
||||
</template>
|
||||
@@ -0,0 +1,6 @@
|
||||
Template.conditionViewDialog.events({
|
||||
"click #deleteButton": function(event, instance){
|
||||
Conditions.remove(instance.data.condition._id);
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
<template name="customBuffEdit">
|
||||
{{#baseEditDialog title=buff.name hideColor=true}}
|
||||
<!--name-->
|
||||
<paper-input id="buffNameInput" class="fullwidth" label="Name" value={{buff.name}}></paper-input>
|
||||
|
||||
<div class="layout horizontal center wrap justified">
|
||||
<paper-dropdown-menu class=flex label="Target" style="flex-basis: 150px; max-width: 200px;">
|
||||
<dicecloud-selector selected={{buff.target}} class="dropdown-content target-dropdown">
|
||||
<paper-item name="self" style="width: 150px;">
|
||||
Self only
|
||||
</paper-item>
|
||||
<paper-item name="others">
|
||||
Others only
|
||||
</paper-item>
|
||||
<paper-item name="both">
|
||||
Both
|
||||
</paper-item>
|
||||
</dicecloud-selector>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
|
||||
<!--description-->
|
||||
<paper-textarea label="Description" id="buffDescriptionInput" value={{buff.description}}></paper-textarea>
|
||||
|
||||
{{> effectsEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
|
||||
{{> attackEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
|
||||
{{> proficiencyEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId enabled=false}}
|
||||
{{/baseEditDialog}}
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
Template.customBuffEdit.helpers({
|
||||
buff(){
|
||||
return CustomBuffs.findOne(this.customBuffId);
|
||||
},
|
||||
});
|
||||
|
||||
const debounce = (f) => _.debounce(f, 300);
|
||||
|
||||
Template.customBuffEdit.events({
|
||||
"input #buffNameInput": debounce(function(event){
|
||||
const input = event.currentTarget;
|
||||
var name = input.value;
|
||||
if (!name){
|
||||
input.invalid = true;
|
||||
input.errorMessage = "Name is required";
|
||||
} else {
|
||||
input.invalid = false;
|
||||
CustomBuffs.update(this.customBuffId, {
|
||||
$set: {name: name}
|
||||
}, {
|
||||
removeEmptyStrings: false,
|
||||
trimStrings: false,
|
||||
});
|
||||
}
|
||||
}),
|
||||
"input #buffDescriptionInput": debounce(function(event){
|
||||
var description = event.currentTarget.value;
|
||||
CustomBuffs.update(this.customBuffId, {
|
||||
$set: {description: description}
|
||||
}, {
|
||||
removeEmptyStrings: false,
|
||||
trimStrings: false,
|
||||
});
|
||||
}),
|
||||
"iron-select .target-dropdown": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
var value = detail.item.getAttribute("name");
|
||||
const buff = CustomBuffs.findOne(this.customBuffId);
|
||||
if (value === buff.target) return;
|
||||
CustomBuffs.update(this.customBuffId, {$set: {target: value}});
|
||||
},
|
||||
"click #deleteButton": function(event, instance){
|
||||
CustomBuffs.softRemoveNode(instance.data.customBuffId);
|
||||
GlobalUI.deletedToast(instance.data.customBuffId, "Buffs", "Buff");
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--needs to be given charId, parentId and parentCollection-->
|
||||
<template name="customBuffEditList">
|
||||
{{#if buffs.count}}
|
||||
<div class="buffs">
|
||||
<div class="paper-font-title" style="margin-bottom: 8px;">
|
||||
Buffs
|
||||
</div>
|
||||
<table class="wideTable" style="width: 100%;">
|
||||
{{#each buff in buffs}}
|
||||
{{> customBuffEditListItem buff=buff}}
|
||||
{{/each}}
|
||||
</table>
|
||||
</div>
|
||||
{{/if}}
|
||||
<paper-button id="addBuffButton"
|
||||
class="red-button"
|
||||
raised>
|
||||
Add Buff
|
||||
</paper-button>
|
||||
</template>
|
||||
|
||||
<template name="customBuffEditListItem">
|
||||
<div class="buff layout horizontal center" data-id={{buff._id}}>
|
||||
{{> customBuffView buff=buff}}
|
||||
<div>
|
||||
<paper-icon-button class="edit-buff" icon="create">
|
||||
</paper-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,41 @@
|
||||
Template.customBuffEditList.helpers({
|
||||
buffs: function(){
|
||||
var selector = {
|
||||
"parent.id": this.parentId,
|
||||
"charId": this.charId,
|
||||
};
|
||||
return CustomBuffs.find(selector);
|
||||
}
|
||||
});
|
||||
|
||||
Template.customBuffEditList.events({
|
||||
"tap #addBuffButton": function(event, instance){
|
||||
if (!_.isBoolean(this.enabled)) {
|
||||
this.enabled = true;
|
||||
}
|
||||
const customBuffId = CustomBuffs.insert({
|
||||
name: this.name || "New Buff",
|
||||
charId: this.charId,
|
||||
parent: {
|
||||
id: this.parentId,
|
||||
collection: this.parentCollection,
|
||||
},
|
||||
});
|
||||
pushDialogStack({
|
||||
template: "customBuffEdit",
|
||||
data: {customBuffId},
|
||||
element: event.currentTarget,
|
||||
returnElement: () => instance.find(`tr.buff[data-id='${customBuffId}']`),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Template.customBuffEditListItem.events({
|
||||
"tap .edit-buff": function(event, template){
|
||||
pushDialogStack({
|
||||
template: "customBuffEdit",
|
||||
data: {customBuffId: this.buff._id},
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
<template name="customBuffView">
|
||||
<div class="flex">{{buff.name}}</div>
|
||||
<div class="flex">
|
||||
{{#if canEditCharacter buff.charId}}
|
||||
<paper-button class="apply-buff-button">Apply{{toSelf}}</paper-button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,82 @@
|
||||
const applyBuff = function(targetId, buff) {
|
||||
var parent = global[buff.parent.collection].findOne(buff.parent.id);
|
||||
|
||||
//insert new buff
|
||||
newBuffId = Buffs.insert({
|
||||
charId: targetId,
|
||||
name: buff.name,
|
||||
description: buff.description,
|
||||
lifeTime: {total: buff.lifeTime.total},
|
||||
type: "custom",
|
||||
|
||||
appliedBy: buff.charId,
|
||||
appliedByDetails: {
|
||||
name: parent.name,
|
||||
collection: buff.parent.collection,
|
||||
},
|
||||
});
|
||||
|
||||
//insert children
|
||||
Attacks.find({"parent.id": buff._id}).forEach(function(doc){
|
||||
temp = _.clone(doc);
|
||||
temp.parent.id = newBuffId;
|
||||
temp.parent.collection = "Buffs";
|
||||
delete temp._id;
|
||||
|
||||
Attacks.insert(temp);
|
||||
});
|
||||
Effects.find({"parent.id": buff._id}).forEach(function(doc){
|
||||
temp = _.clone(doc);
|
||||
temp.parent.id = newBuffId;
|
||||
temp.parent.collection = "Buffs";
|
||||
delete temp._id;
|
||||
|
||||
Effects.insert(temp);
|
||||
});
|
||||
Proficiencies.find({"parent.id": buff._id}).forEach(function(doc){
|
||||
temp = _.clone(doc);
|
||||
temp.parent.id = newBuffId;
|
||||
temp.parent.collection = "Buffs";
|
||||
delete temp._id;
|
||||
|
||||
Proficiencies.insert(temp);
|
||||
});
|
||||
|
||||
let target;
|
||||
if (targetId == buff.charId) {
|
||||
target = "self";
|
||||
} else {
|
||||
target = Characters.findOne(targetId) || {};
|
||||
target = target && target.name || "target"
|
||||
}
|
||||
GlobalUI.toast(`${buff.name || "Buff"} applied to ${target}`);
|
||||
};
|
||||
|
||||
Template.customBuffView.helpers({
|
||||
toSelf: function() {
|
||||
if (this.buff.target === "self") {
|
||||
return " to self";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.customBuffView.events({
|
||||
"click .apply-buff-button": function(){
|
||||
if (this.buff.target !== "self") {
|
||||
pushDialogStack({
|
||||
template: "applyBuffDialog",
|
||||
data: {buff: this.buff},
|
||||
element: event.currentTarget,
|
||||
callback: (targetId) => {
|
||||
if (!targetId) return;
|
||||
applyBuff(targetId, this.buff);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
var targetId = this.buff.charId;
|
||||
applyBuff(targetId, this.buff);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
<template name="customBuffViewList">
|
||||
{{#if buffs.count}}
|
||||
<div class="buffs">
|
||||
<div class="paper-font-title" style="margin-bottom: 8px;">
|
||||
Buffs
|
||||
</div>
|
||||
{{#each buff in buffs}}
|
||||
<div class="layout horizontal center">
|
||||
{{> customBuffView buff=buff}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
Template.customBuffViewList.helpers({
|
||||
buffs: function(){
|
||||
var selector = {
|
||||
"parent.id": this.parentId,
|
||||
"charId": this.charId,
|
||||
};
|
||||
return CustomBuffs.find(selector);
|
||||
}
|
||||
});
|
||||
@@ -14,6 +14,9 @@
|
||||
<paper-toggle-button id="variantEncumbrance" checked={{settings.useVariantEncumbrance}}>
|
||||
Use variant encumbrance
|
||||
</paper-toggle-button>
|
||||
<paper-toggle-button id="swapStatAndModifier" checked={{settings.swapStatAndModifier}}>
|
||||
Swap stats and modifiers on Stats page
|
||||
</paper-toggle-button>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
|
||||
@@ -23,6 +23,15 @@ Template.characterSettings.events({
|
||||
);
|
||||
}
|
||||
},
|
||||
"change #swapStatAndModifier": function(event, instance){
|
||||
var value = instance.find("#swapStatAndModifier").checked;
|
||||
if (this.settings.swapStatAndModifier !== value){
|
||||
Characters.update(
|
||||
this._id,
|
||||
{$set: {"settings.swapStatAndModifier": value}}
|
||||
);
|
||||
}
|
||||
},
|
||||
"click .doneButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ Template.deleteCharacterConfirmation.helpers({
|
||||
if (Template.instance().canDelete.get()) {
|
||||
return "background: #d23f31; color: white;";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.deleteCharacterConfirmation.events({
|
||||
@@ -20,9 +20,7 @@ Template.deleteCharacterConfirmation.events({
|
||||
},
|
||||
"click #deleteButton": function(event, instance) {
|
||||
if (instance.find("#nameInput").value === this.name) {
|
||||
popDialogStack();
|
||||
Router.go("/characterList");
|
||||
Characters.remove(this._id);
|
||||
popDialogStack(true);
|
||||
}
|
||||
},
|
||||
"click .cancelButton": function(event, instance){
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- shamelessly nicked and renamed from deleteCharacterConfirmation.html -->
|
||||
<template name="unshareCharacterConfirmation">
|
||||
<div class="fit layout vertical">
|
||||
<app-header-layout has-scrolling-region class="feedback flex">
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar>
|
||||
<div main-title>Unshare Character</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="form flex">
|
||||
Removing (unsharing) a character does not delete it.<br>
|
||||
However, you will be no longer be able to access or view it, unless it is publicly visible.<br>
|
||||
The character's owner or anyone with write permissions for the character can return read access.<br><br>
|
||||
To continue type "{{name}}" into the box below.<br>
|
||||
<paper-input id="nameInput" label="type the characters's name here" style="width: 100%;"></paper-input><br>
|
||||
<paper-button id="unshareButton" style={{getStyle}} disabled={{cantUnshare}}>Unshare Character</paper-button>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
<paper-button class="cancelButton"> Cancel </paper-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,31 @@
|
||||
Template.unshareCharacterConfirmation.onCreated(function() {
|
||||
this.canUnshare = new ReactiveVar(false);
|
||||
});
|
||||
|
||||
Template.unshareCharacterConfirmation.helpers({
|
||||
cantUnshare: function() {
|
||||
return !Template.instance().canUnshare.get();
|
||||
},
|
||||
getStyle: function() {
|
||||
if (Template.instance().canUnshare.get()) {
|
||||
return "background: #d23f31; color: white;";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.unshareCharacterConfirmation.events({
|
||||
"change #nameInput, input #nameInput": function(event, instance) {
|
||||
var can = instance.find("#nameInput").value === this.name;
|
||||
instance.canUnshare.set(can);
|
||||
},
|
||||
"click #unshareButton": function(event, instance) {
|
||||
if (instance.find("#nameInput").value === this.name) {
|
||||
setTimeout(popDialogStack, 100); //weird things happen without the delay.
|
||||
Router.go("/characterList");
|
||||
Meteor.call("removeMeFromReaders", this._id);
|
||||
}
|
||||
},
|
||||
"click .cancelButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
<template name="characterSheet">
|
||||
<div class="fit layout vertical character-sheet">
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar class="medium-tall {{colorClass}}">
|
||||
<app-toolbar class="medium-tall {{colorClass}}" style="z-index: 2;">
|
||||
<div top-item class="layout horizontal center">
|
||||
<paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
|
||||
<div class="flex character-name">
|
||||
@@ -24,23 +24,38 @@
|
||||
<iron-icon icon="settings" item-icon></iron-icon>
|
||||
Settings
|
||||
</paper-icon-item>
|
||||
<paper-icon-item id="characterExport">
|
||||
<iron-icon icon="content-copy" item-icon></iron-icon>
|
||||
Export to Improved Initiative
|
||||
</paper-icon-item>
|
||||
</paper-menu>
|
||||
</paper-menu-button>
|
||||
{{else}}
|
||||
<paper-menu-button class="character-menu" horizontal-align="right">
|
||||
<paper-icon-button icon="more-vert" class="dropdown-trigger"></paper-icon-button>
|
||||
<paper-menu class="dropdown-content black87">
|
||||
<paper-icon-item id="unshareCharacter">
|
||||
<iron-icon icon="delete" item-icon></iron-icon>
|
||||
Unshare
|
||||
</paper-icon-item>
|
||||
</paper-menu>
|
||||
</paper-menu-button>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div bottom-item>
|
||||
<paper-tabs id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
|
||||
<paper-tab name="stats">Stats</paper-tab>
|
||||
<paper-tab name="features">Features</paper-tab>
|
||||
<paper-tab name="stats" class="{{#if shouldBounce 0}}bounce{{/if}}">Stats</paper-tab>
|
||||
<paper-tab name="features" class="{{#if shouldBounce 1}}bounce{{/if}}">Features</paper-tab>
|
||||
<paper-tab name="inventory">Inventory</paper-tab>
|
||||
{{#unless hideSpellcasting}}
|
||||
<paper-tab name="spells">Spells</paper-tab>
|
||||
{{/unless}}
|
||||
<paper-tab name="persona">Persona</paper-tab>
|
||||
<paper-tab name="journal">Journal</paper-tab>
|
||||
<paper-tab name="journal" class="{{#if shouldBounce 5}}bounce{{/if}}">Journal</paper-tab>
|
||||
</paper-tabs>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
{{#if newUserExperience}}{{> newUserStepper}}{{/if}}
|
||||
</app-header>
|
||||
<div class="flex" style="position: relative;">
|
||||
<iron-pages id="tabPages" class="fit" selected={{selectedTab}}>
|
||||
|
||||
@@ -29,7 +29,7 @@ Template.characterSheet.onRendered(function() {
|
||||
tabFabMenus = _.times(6, (n) =>
|
||||
tabPages[n].find(".mini-holder")
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
//watch this character and make sure their encumbrance is updated
|
||||
//trackEncumbranceConditions(this.data._id, this);
|
||||
@@ -172,6 +172,18 @@ Template.characterSheet.helpers({
|
||||
var char = Characters.findOne(this._id);
|
||||
return char && char.settings.hideSpellcasting;
|
||||
},
|
||||
newUserExperience: function(){
|
||||
var char = Characters.findOne(this._id);
|
||||
return char && char.settings.newUserExperience;
|
||||
},
|
||||
shouldBounce: function(tab){
|
||||
const selected = Session.get(this._id + ".selectedTab")
|
||||
const step = Session.get("newUserExperienceStep");
|
||||
if (selected == tab) return false;
|
||||
return (tab === 1 && step === 0) ||
|
||||
(tab === 5 && step === 1) ||
|
||||
(tab === 0 && step === 2);
|
||||
},
|
||||
});
|
||||
|
||||
Template.characterSheet.events({
|
||||
@@ -187,6 +199,12 @@ Template.characterSheet.events({
|
||||
data: this,
|
||||
template: "deleteCharacterConfirmation",
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
callback: (result) => {
|
||||
if (result === true){
|
||||
Router.go("/characterList");
|
||||
Tracker.afterFlush(() => Characters.remove(this._id));
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
"click #shareCharacter": function(event, instance){
|
||||
@@ -203,4 +221,18 @@ Template.characterSheet.events({
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
"click #characterExport": function(event, instance){
|
||||
pushDialogStack({
|
||||
data: this,
|
||||
template: "exportDialog",
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
"click #unshareCharacter": function(event, instance){
|
||||
pushDialogStack({
|
||||
data: this,
|
||||
template: "unshareCharacterConfirmation",
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
label="Value"
|
||||
floatinglabel
|
||||
value={{effectValue}}>
|
||||
{{> formulaSuffix}}
|
||||
</paper-input>
|
||||
{{else}}
|
||||
<div style="height: 62px;"></div>
|
||||
@@ -20,26 +21,28 @@
|
||||
<div class="effectEdit layout horizontal flex">
|
||||
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
|
||||
{{#each statGroups}}
|
||||
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
||||
<div class="statGroupTitle clickable" style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
||||
{{this}}
|
||||
</div>
|
||||
{{#each stats}}
|
||||
<paper-item name={{stat}}>{{name}}</paper-item>
|
||||
{{/each}}
|
||||
<iron-collapse opened={{isGroupSelected this ../stat}}>
|
||||
{{#each stats}}
|
||||
<paper-item name={{stat}} class="clickable">{{name}}</paper-item>
|
||||
{{/each}}
|
||||
</iron-collapse>
|
||||
{{/each}}
|
||||
</dicecloud-selector>
|
||||
{{#if operations}}
|
||||
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
|
||||
{{#each operations}}
|
||||
<paper-item name={{operation}}>{{name}}</paper-item>
|
||||
<paper-item name={{operation}} class="clickable">{{name}}</paper-item>
|
||||
{{/each}}
|
||||
</dicecloud-selector>
|
||||
{{else}} {{#if showMultiplierOperations}}
|
||||
<dicecloud-selector class="multiplierMenu flex"
|
||||
selected={{value}}>
|
||||
<paper-item name="0.5">Resistance</paper-item>
|
||||
<paper-item name="2">Vulnerability</paper-item>
|
||||
<paper-item name="0">Immunity</paper-item>
|
||||
<paper-item name="0.5" class="clickable">Resistance</paper-item>
|
||||
<paper-item name="2" class="clickable">Vulnerability</paper-item>
|
||||
<paper-item name="0" class="clickable">Immunity</paper-item>
|
||||
</dicecloud-selector>
|
||||
{{else}}
|
||||
<div class="flex" style="height: 100%;"></div>
|
||||
|
||||
@@ -7,32 +7,36 @@ var stats = [
|
||||
{stat: "intelligence", name: "Intelligence", group: "Ability Scores"},
|
||||
{stat: "wisdom", name: "Wisdom", group: "Ability Scores"},
|
||||
{stat: "charisma", name: "Charisma", group: "Ability Scores"},
|
||||
{name: "Strength Save", stat: "strengthSave", group: "Saving Throws"},
|
||||
{name: "Dexterity Save", stat: "dexteritySave", group: "Saving Throws"},
|
||||
{name: "Constitution Save", stat: "constitutionSave", group: "Saving Throws"},
|
||||
{name: "Intelligence Save", stat: "intelligenceSave", group: "Saving Throws"},
|
||||
{name: "Wisdom Save", stat: "wisdomSave", group: "Saving Throws"},
|
||||
{name: "Charisma Save", stat: "charismaSave", group: "Saving Throws"},
|
||||
{name: "Acrobatics", stat: "acrobatics", group: "Skills"},
|
||||
{name: "Animal Handling", stat: "animalHandling", group: "Skills"},
|
||||
{name: "Arcana", stat: "arcana", group: "Skills"},
|
||||
{name: "Athletics", stat: "athletics", group: "Skills"},
|
||||
{name: "Deception", stat: "deception", group: "Skills"},
|
||||
{name: "History", stat: "history", group: "Skills"},
|
||||
{name: "Insight", stat: "insight", group: "Skills"},
|
||||
{name: "Intimidation", stat: "intimidation", group: "Skills"},
|
||||
{name: "Investigation", stat: "investigation", group: "Skills"},
|
||||
{name: "Medicine", stat: "medicine", group: "Skills"},
|
||||
{name: "Nature", stat: "nature", group: "Skills"},
|
||||
{name: "Perception", stat: "perception", group: "Skills"},
|
||||
{name: "Performance", stat: "performance", group: "Skills"},
|
||||
{name: "Persuasion", stat: "persuasion", group: "Skills"},
|
||||
{name: "Religion", stat: "religion", group: "Skills"},
|
||||
{name: "Sleight of Hand", stat: "sleightOfHand", group: "Skills"},
|
||||
{name: "Stealth", stat: "stealth", group: "Skills"},
|
||||
{name: "Survival", stat: "survival", group: "Skills"},
|
||||
{name: "Initiative", stat: "initiative", group: "Skills"},
|
||||
|
||||
{stat: "strengthSave", name: "Strength Save", group: "Saving Throws"},
|
||||
{stat: "dexteritySave", name: "Dexterity Save", group: "Saving Throws"},
|
||||
{stat: "constitutionSave", name: "Constitution Save", group: "Saving Throws"},
|
||||
{stat: "intelligenceSave", name: "Intelligence Save", group: "Saving Throws"},
|
||||
{stat: "wisdomSave", name: "Wisdom Save", group: "Saving Throws"},
|
||||
{stat: "charismaSave", name: "Charisma Save", group: "Saving Throws"},
|
||||
|
||||
{stat: "acrobatics", name: "Acrobatics", group: "Skills"},
|
||||
{stat: "animalHandling", name: "Animal Handling", group: "Skills"},
|
||||
{stat: "arcana", name: "Arcana", group: "Skills"},
|
||||
{stat: "athletics", name: "Athletics", group: "Skills"},
|
||||
{stat: "deception", name: "Deception", group: "Skills"},
|
||||
{stat: "history", name: "History", group: "Skills"},
|
||||
{stat: "insight", name: "Insight", group: "Skills"},
|
||||
{stat: "intimidation", name: "Intimidation", group: "Skills"},
|
||||
{stat: "investigation", name: "Investigation", group: "Skills"},
|
||||
{stat: "medicine", name: "Medicine", group: "Skills"},
|
||||
{stat: "nature", name: "Nature", group: "Skills"},
|
||||
{stat: "perception", name: "Perception", group: "Skills"},
|
||||
{stat: "performance", name: "Performance", group: "Skills"},
|
||||
{stat: "persuasion", name: "Persuasion", group: "Skills"},
|
||||
{stat: "religion", name: "Religion", group: "Skills"},
|
||||
{stat: "sleightOfHand", name: "Sleight of Hand", group: "Skills"},
|
||||
{stat: "stealth", name: "Stealth", group: "Skills"},
|
||||
{stat: "survival", name: "Survival", group: "Skills"},
|
||||
{stat: "initiative", name: "Initiative", group: "Skills"},
|
||||
|
||||
{stat: "hitPoints", name: "Hit Points", group: "Stats"},
|
||||
{stat: "tempHP", name: "Temporary Hit Points", group: "Stats"},
|
||||
{stat: "armor", name: "Armor", group: "Stats"},
|
||||
{stat: "dexterityArmor", name: "Dexterity Armor Bonus", group: "Stats"},
|
||||
{stat: "speed", name: "Speed", group: "Stats"},
|
||||
@@ -44,6 +48,7 @@ var stats = [
|
||||
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
|
||||
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
|
||||
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
|
||||
|
||||
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
|
||||
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
|
||||
{stat: "level3SpellSlots", name: "level 3", group: "Spell Slots"},
|
||||
@@ -53,10 +58,12 @@ var stats = [
|
||||
{stat: "level7SpellSlots", name: "level 7", group: "Spell Slots"},
|
||||
{stat: "level8SpellSlots", name: "level 8", group: "Spell Slots"},
|
||||
{stat: "level9SpellSlots", name: "level 9", group: "Spell Slots"},
|
||||
{stat: "d6HitDice", name: "d6", group: "Hit Dice"},
|
||||
{stat: "d8HitDice", name: "d8", group: "Hit Dice"},
|
||||
{stat: "d10HitDice", name: "d10", group: "Hit Dice"},
|
||||
{stat: "d12HitDice", name: "d12", group: "Hit Dice"},
|
||||
|
||||
{stat: "d6HitDice", name: "d6 Hit Dice", group: "Hit Dice"},
|
||||
{stat: "d8HitDice", name: "d8 Hit Dice", group: "Hit Dice"},
|
||||
{stat: "d10HitDice", name: "d10 Hit Dice", group: "Hit Dice"},
|
||||
{stat: "d12HitDice", name: "d12 Hit Dice", group: "Hit Dice"},
|
||||
|
||||
{stat: "acidMultiplier", name: "Acid", group: "Weakness/Resistance"},
|
||||
{stat: "bludgeoningMultiplier", name: "Bludgeoning", group: "Weakness/Resistance"},
|
||||
{stat: "coldMultiplier", name: "Cold", group: "Weakness/Resistance"},
|
||||
@@ -149,47 +156,71 @@ Template.effectEdit.helpers({
|
||||
effectValue: function(){
|
||||
return this.calculation || this.value;
|
||||
},
|
||||
isGroupSelected: function(groupName, statName){
|
||||
var stat = statsDict[statName]
|
||||
return stat && (stat.group === groupName);
|
||||
},
|
||||
});
|
||||
|
||||
var setStat = function(statName, effectId){
|
||||
var setter = {stat: statName};
|
||||
var effect = Effects.findOne(this._id);
|
||||
var group = statsDict[statName].group;
|
||||
if (group === "Saving Throws" || group === "Skills"){
|
||||
// Skills must have a valid skill operation
|
||||
if (!_.contains(
|
||||
_.map(skillOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "add";
|
||||
}
|
||||
} else if (group !== "Weakness/Resistance"){
|
||||
// Attributes must have a valid attribute operation
|
||||
if (!_.contains(
|
||||
_.map(attributeOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "base";
|
||||
}
|
||||
} else {
|
||||
// Weakness/Resistance must have a mul operation and value
|
||||
if (effect.operation !== "mul"){
|
||||
setter.operation = "mul";
|
||||
}
|
||||
if (!_.contains([0, 0.5, 2], effect.value)){
|
||||
setter.value = 0.5;
|
||||
}
|
||||
}
|
||||
Effects.update(effectId, {$set: setter});
|
||||
};
|
||||
|
||||
var scrollAnimationId;
|
||||
var scrollElementToView = element => {
|
||||
var scrollFunction = function(){
|
||||
element.scrollIntoView();
|
||||
scrollAnimationId = requestAnimationFrame(scrollFunction);
|
||||
};
|
||||
return scrollFunction;
|
||||
}
|
||||
|
||||
Template.effectEdit.events({
|
||||
"click #deleteButton": function(event, instance){
|
||||
Effects.softRemoveNode(instance.data.id);
|
||||
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
|
||||
popDialogStack();
|
||||
},
|
||||
"click .statGroupTitle": function(event, instance){
|
||||
var groupName = this.toString();
|
||||
var firstStat = statGroups[groupName][0].stat;
|
||||
setStat(firstStat, instance.data.id);
|
||||
scrollAnimationId = requestAnimationFrame(scrollElementToView(event.target));
|
||||
_.delay(() => cancelAnimationFrame(scrollAnimationId), 300);
|
||||
},
|
||||
"iron-select .statMenu": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
var statName = detail.item.getAttribute("name");
|
||||
if (statName == this.stat) return;
|
||||
var setter = {stat: statName};
|
||||
var group = Blaze.getData(detail.item).group;
|
||||
var effect = Effects.findOne(this._id);
|
||||
if (group === "Saving Throws" || group === "Skills"){
|
||||
// Skills must have a valid skill operation
|
||||
if (!_.contains(
|
||||
_.map(skillOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "add";
|
||||
}
|
||||
} else if (group !== "Weakness/Resistance"){
|
||||
// Attributes must have a valid attribute operation
|
||||
if (!_.contains(
|
||||
_.map(attributeOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "base";
|
||||
}
|
||||
} else {
|
||||
// Weakness/Resistance must have a mul operation and value
|
||||
if (effect.operation !== "mul"){
|
||||
setter.operation = "mul";
|
||||
}
|
||||
if (!_.contains([0, 0.5, 2], effect.value)){
|
||||
setter.value = 0.5;
|
||||
}
|
||||
}
|
||||
Effects.update(this._id, {$set: setter});
|
||||
setStat(statName, this._id);
|
||||
},
|
||||
"iron-select .operationMenu": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
|
||||
@@ -5,12 +5,14 @@ var stats = {
|
||||
"intelligence":{"name":"Intelligence"},
|
||||
"wisdom":{"name":"Wisdom"},
|
||||
"charisma":{"name":"Charisma"},
|
||||
|
||||
"strengthSave":{"name":"Strength Save"},
|
||||
"dexteritySave":{"name":"Dexterity Save"},
|
||||
"constitutionSave":{"name":"Constitution Save"},
|
||||
"intelligenceSave":{"name":"Intelligence Save"},
|
||||
"wisdomSave":{"name":"Wisdom Save"},
|
||||
"charismaSave":{"name":"Charisma Save"},
|
||||
|
||||
"acrobatics":{"name":"Acrobatics"},
|
||||
"animalHandling":{"name":"Animal Handling"},
|
||||
"arcana":{"name":"Arcana"},
|
||||
@@ -30,7 +32,9 @@ var stats = {
|
||||
"stealth":{"name":"Stealth"},
|
||||
"survival":{"name":"Survival"},
|
||||
"initiative":{"name":"Initiative"},
|
||||
|
||||
"hitPoints":{"name":"Hit Points"},
|
||||
"tempHP":{"name":"Temporary Hit Points"},
|
||||
"armor":{"name":"Armor"},
|
||||
"dexterityArmor":{"name":"Dexterity Armor Bonus"},
|
||||
"speed":{"name":"Speed"},
|
||||
@@ -42,6 +46,7 @@ var stats = {
|
||||
"expertiseDice":{"name":"Expertise Dice"},
|
||||
"superiorityDice":{"name":"Superiority Dice"},
|
||||
"carryMultiplier": {"name": "Carry Capacity Multiplier"},
|
||||
|
||||
"level1SpellSlots":{"name":"level 1 Spell Slots"},
|
||||
"level2SpellSlots":{"name":"level 2 Spell Slots"},
|
||||
"level3SpellSlots":{"name":"level 3 Spell Slots"},
|
||||
@@ -51,10 +56,12 @@ var stats = {
|
||||
"level7SpellSlots":{"name":"level 7 Spell Slots"},
|
||||
"level8SpellSlots":{"name":"level 8 Spell Slots"},
|
||||
"level9SpellSlots":{"name":"level 9 Spell Slots"},
|
||||
|
||||
"d6HitDice":{"name":"d6 Hit Dice"},
|
||||
"d8HitDice":{"name":"d8 Hit Dice"},
|
||||
"d10HitDice":{"name":"d10 Hit Dice"},
|
||||
"d12HitDice":{"name":"d12 Hit Dice"},
|
||||
|
||||
"acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
|
||||
"bludgeoningMultiplier":{
|
||||
"name":"Bludgeoning damage", "group": "Weakness/Resistance",
|
||||
|
||||
@@ -33,7 +33,7 @@ Template.effectsEditList.events({
|
||||
template: "effectEdit",
|
||||
data: {id: effectId},
|
||||
element: event.currentTarget,
|
||||
returnElement: instance.find(`tr.effect[data-id='${effectId}']`),
|
||||
returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`),
|
||||
});
|
||||
},
|
||||
"tap .edit-effect": function(event, template){
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.exportDialog .iiexport {
|
||||
overflow-y: auto;
|
||||
width: 100% !important;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<template name="exportDialog">
|
||||
<div class="exportDialog fit layout vertical">
|
||||
{{#with character}}
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar>
|
||||
<div main-title>Export Character to Improved Initiative</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="form flex layout vertical">
|
||||
<paper-toggle-button id="exportFeatures" checked={{settings.exportFeatures}}>
|
||||
Features
|
||||
</paper-toggle-button>
|
||||
<paper-toggle-button id="exportAttacks" checked={{settings.exportAttacks}}>
|
||||
Attacks
|
||||
</paper-toggle-button>
|
||||
<paper-toggle-button id="exportDescription" checked={{settings.exportDescription}}>
|
||||
Description
|
||||
</paper-toggle-button>
|
||||
<div class="paper-font-title padded">JSON</div>
|
||||
<textarea class="flex iiexport">{{improvedInitiativeJson}}</textarea>
|
||||
<paper-button id="copyExportButton" class="red-button" raised>
|
||||
<iron-icon icon="content-copy"></iron-icon>
|
||||
Copy to Clipboard
|
||||
</paper-button>
|
||||
</div>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
<paper-button class="doneButton"> Done </paper-button>
|
||||
</div>
|
||||
{{/with}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
Template.exportDialog.helpers({
|
||||
character: function(){
|
||||
return Characters.findOne(this._id);
|
||||
},
|
||||
improvedInitiativeJson: function(){
|
||||
var options = {
|
||||
features: this.settings.exportFeatures,
|
||||
attacks: this.settings.exportAttacks,
|
||||
description: this.settings.exportDescription,
|
||||
}
|
||||
return improvedInitiativeJson(this._id, options);
|
||||
},
|
||||
});
|
||||
|
||||
Template.exportDialog.events({
|
||||
"change #exportFeatures": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportFeatures": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"change #exportAttacks": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportAttacks": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"change #exportDescription": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportDescription": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"click #copyExportButton": function(event, template){
|
||||
var copyTextarea = template.find(".iiexport");
|
||||
copyTextarea.select();
|
||||
var msg;
|
||||
try {
|
||||
var successful = document.execCommand("copy");
|
||||
var msg = successful ? "JSON copied to clipboard" : "Unable to copy JSON";
|
||||
} catch (err) {
|
||||
msg = "Unable to copy JSON";
|
||||
} finally {
|
||||
clearSelection();
|
||||
GlobalUI.toast(msg);
|
||||
}
|
||||
},
|
||||
"click .doneButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
|
||||
var clearSelection = function(){
|
||||
if (window.getSelection) {
|
||||
if (window.getSelection().empty) { // Chrome
|
||||
window.getSelection().empty();
|
||||
} else if (window.getSelection().removeAllRanges) { // Firefox
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
} else if (document.selection) { // IE?
|
||||
document.selection.empty();
|
||||
}
|
||||
};
|
||||
@@ -36,12 +36,26 @@
|
||||
|
||||
{{> effectsViewList charId=charId parentId=_id}}
|
||||
{{> proficiencyViewList charId=charId parentId=_id}}
|
||||
{{> attacksViewList charId=charId parentId=_id}}
|
||||
{{> customBuffViewList charId=charId parentId=_id}}
|
||||
|
||||
</template>
|
||||
|
||||
<template name="featureEdit">
|
||||
{{#if showNewUserExperience}}
|
||||
{{# infoBox}}
|
||||
<p>
|
||||
Features represent all the permanent things your character can do.
|
||||
</p><p>
|
||||
A feature can change a character's stats with effects,
|
||||
or give the character proficiencies, attacks, and buffs.
|
||||
</p><p>
|
||||
Give the feature a name, and close it to continue.
|
||||
</p>
|
||||
{{/infoBox}}
|
||||
{{/if}}
|
||||
<!--name-->
|
||||
<paper-input id="featureNameInput" class="fullwidth" label="Name" value={{name}}></paper-input>
|
||||
|
||||
<div class="layout horizontal center wrap justified">
|
||||
<paper-dropdown-menu class=flex label="Enable Feature" style="flex-basis: 150px; max-width: 200px;">
|
||||
<dicecloud-selector selected={{enabledSelection}} class="dropdown-content enabled-dropdown">
|
||||
@@ -68,9 +82,14 @@
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<!--description-->
|
||||
<paper-textarea label="Description" id="featureDescriptionInput" value={{description}}></paper-textarea>
|
||||
<!--Description-->
|
||||
<div class="description-input layout horizontal end">
|
||||
<paper-textarea id="featureDescriptionInput" label="Description" value={{description}}></paper-textarea>
|
||||
{{> textareaBracketSuffix}}
|
||||
</div>
|
||||
|
||||
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
|
||||
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
|
||||
{{> attackEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled name=name}}
|
||||
{{> customBuffEditList parentId=_id parentCollection="Features" charId=charId}}
|
||||
</template>
|
||||
|
||||
@@ -47,6 +47,10 @@ Template.featureDetails.events({
|
||||
});
|
||||
|
||||
Template.featureEdit.helpers({
|
||||
showNewUserExperience: function(){
|
||||
return Session.get("newUserExperienceStep") === 0 ||
|
||||
Session.get("newUserExperienceStep") === 1;
|
||||
},
|
||||
usesSet: function(){
|
||||
return _.isString(this.uses);
|
||||
},
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card.featureCard .bottom {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.containerMain.featureDescription {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
@@ -19,30 +19,8 @@
|
||||
Attacks
|
||||
</div>
|
||||
<div class="bottom list">
|
||||
{{#each attacks}}
|
||||
<div class="item-slot">
|
||||
<div class="flexible attack item">
|
||||
<div class="layout horizontal">
|
||||
<div class="paper-font-headline layout horizontal center"
|
||||
style="margin-right: 16px;">
|
||||
{{evaluateSigned ../_id attackBonus}}
|
||||
</div>
|
||||
<div class="flex layout vertical">
|
||||
<div class="paper-font-body2">
|
||||
{{name}}
|
||||
</div>
|
||||
<div>
|
||||
{{evaluateString ../_id damage}} {{damageType}}
|
||||
</div>
|
||||
{{#if details}}
|
||||
<div>
|
||||
{{details}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{#each attack in attacks}}
|
||||
{{>attackListItem attack=attack charId=_id}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</paper-material>
|
||||
@@ -55,19 +33,19 @@
|
||||
Proficiencies
|
||||
</div>
|
||||
<div flex class="bottom list">
|
||||
{{#if weaponProfs.count}}
|
||||
{{#if weaponProfs.length}}
|
||||
<div class="paper-font-subhead">Weapons</div>
|
||||
{{/if}}
|
||||
{{#each weaponProfs}}
|
||||
{{> proficiencyListItem}}
|
||||
{{/each}}
|
||||
{{#if armorProfs.count}}
|
||||
{{#if armorProfs.length}}
|
||||
<div class="paper-font-subhead">Armor</div>
|
||||
{{/if}}
|
||||
{{#each armorProfs}}
|
||||
{{> proficiencyListItem}}
|
||||
{{/each}}
|
||||
{{#if toolProfs.count}}
|
||||
{{#if toolProfs.length}}
|
||||
<div class="paper-font-subhead">Tools</div>
|
||||
{{/if}}
|
||||
{{#each toolProfs}}
|
||||
@@ -96,13 +74,14 @@
|
||||
checked={{enabled}}
|
||||
disabled={{#unless canEditCharacter charId}}true{{/unless}}>
|
||||
</paper-checkbox>
|
||||
<paper-tooltip position="left">Feature enabled</paper-tooltip>
|
||||
{{#simpleTooltip}}Feature enabled{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if description}}
|
||||
{{#if hasCharacters (evaluateShortString charId description)}}
|
||||
<div class="bottom flex">
|
||||
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
|
||||
{{> customBuffViewList charId=charId parentId=_id}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if hasUses}}
|
||||
@@ -122,11 +101,13 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab id="addFeature"
|
||||
class="floatyButton"
|
||||
icon="add">
|
||||
<paper-tooltip position="left">Add Feature</paper-tooltip>
|
||||
</paper-fab>
|
||||
<div class="floatyButton">
|
||||
<paper-fab id="addFeature"
|
||||
class="{{#if shouldFloatyButtonBounce}}bounce{{/if}}"
|
||||
icon="add">
|
||||
</paper-fab>
|
||||
{{#simpleTooltip}}Add Feature{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -156,3 +137,29 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
<template name="attackListItem">
|
||||
<div class="item-slot">
|
||||
<div class="flexible attack item">
|
||||
<div class="layout horizontal">
|
||||
<div class="paper-font-headline layout horizontal center"
|
||||
style="margin-right: 16px;">
|
||||
{{evaluateAttackBonus charId attack}}
|
||||
</div>
|
||||
<div class="flex layout vertical">
|
||||
<div class="paper-font-body2">
|
||||
{{attack.name}}
|
||||
</div>
|
||||
<div>
|
||||
{{evaluateDamage charId attack}} {{attack.damageType}}
|
||||
</div>
|
||||
{{#if attack.details}}
|
||||
<div>
|
||||
{{attack.details}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
var removeDuplicateProficiencies = function(proficiencies) {
|
||||
dict = {};
|
||||
proficiencies.forEach(function(prof) {
|
||||
if (prof.name in dict) { //if we have already gone over another proficiency for the same thing
|
||||
if (dict[prof.name].value < prof.value) {
|
||||
dict[prof.name] = prof; //then take the new one if it's higher, otherwise leave it
|
||||
}
|
||||
} else {
|
||||
dict[prof.name] = prof; //if it wasn't already there, store it
|
||||
}
|
||||
});
|
||||
profs = []
|
||||
_.forEach(dict, function(prof) {
|
||||
profs.push(prof);
|
||||
})
|
||||
return profs;
|
||||
};
|
||||
|
||||
Template.features.helpers({
|
||||
features: function(){
|
||||
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
|
||||
@@ -27,13 +45,23 @@ Template.features.helpers({
|
||||
return !this.alwaysEnabled;
|
||||
},
|
||||
weaponProfs: function(){
|
||||
return Proficiencies.find({charId: this._id, type: "weapon"});
|
||||
var profs = Proficiencies.find({charId: this._id, type: "weapon"});
|
||||
return removeDuplicateProficiencies(profs);
|
||||
},
|
||||
armorProfs: function(){
|
||||
return Proficiencies.find({charId: this._id, type: "armor"});
|
||||
var profs = Proficiencies.find({charId: this._id, type: "armor"});
|
||||
return removeDuplicateProficiencies(profs);
|
||||
},
|
||||
toolProfs: function(){
|
||||
return Proficiencies.find({charId: this._id, type: "tool"});
|
||||
var profs = Proficiencies.find({charId: this._id, type: "tool"});
|
||||
return removeDuplicateProficiencies(profs);
|
||||
},
|
||||
hasCharacters: function(string){
|
||||
return string && string.match(/\S/);
|
||||
},
|
||||
shouldFloatyButtonBounce: function(){
|
||||
const step = Session.get("newUserExperienceStep");
|
||||
return step === 0 && Features.find({charId: this._id}).count() <= 1;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -61,13 +89,6 @@ Template.features.events({
|
||||
element: event.currentTarget.parentElement,
|
||||
});
|
||||
},
|
||||
"click .attack": function(event){
|
||||
openParentDialog({
|
||||
parent: this.parent,
|
||||
charId: this.charId,
|
||||
element: event.currentTarget,
|
||||
});
|
||||
},
|
||||
"click .useFeature": function(event){
|
||||
var featureId = this._id;
|
||||
Features.update(featureId, {$inc: {used: 1}});
|
||||
@@ -133,3 +154,42 @@ Template.resource.events({
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
Template.attackListItem.helpers({
|
||||
evaluateAttackBonus: function(charId, attack) {
|
||||
if (attack.parent.collection == "Spells") {
|
||||
var spell = Spells.findOne(attack.parent.id);
|
||||
if (spell) {
|
||||
bonus = evaluate(charId, attack.attackBonus, {"spellListId": spell.parent.id});
|
||||
}
|
||||
} else {
|
||||
var bonus = evaluate(charId, attack.attackBonus);
|
||||
}
|
||||
|
||||
if (_.isFinite(bonus)) {
|
||||
return bonus > 0 ? "+" + bonus : "" + bonus;
|
||||
} else {
|
||||
return bonus;
|
||||
}
|
||||
},
|
||||
evaluateDamage: function(charId, attack) {
|
||||
if (attack.parent.collection == "Spells") {
|
||||
var spell = Spells.findOne(attack.parent.id);
|
||||
if (spell) {
|
||||
return evaluateSpellString(charId, spell.parent.id, attack.damage);
|
||||
}
|
||||
} else {
|
||||
return evaluateString(charId, attack.damage);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.attackListItem.events({
|
||||
"click .attack": function(event, instance){
|
||||
openParentDialog({
|
||||
parent: instance.data.attack.parent,
|
||||
charId: instance.data.charId,
|
||||
element: event.currentTarget,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,12 +17,17 @@
|
||||
</paper-input>
|
||||
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
|
||||
</paper-input>
|
||||
<paper-toggle-button id="carriedToggle" checked={{isCarried}}>
|
||||
Carried
|
||||
</paper-toggle-button>
|
||||
</div>
|
||||
|
||||
<hr class="vertMargin">
|
||||
|
||||
<paper-textarea label="Description" id="containerDescriptionInput" value={{description}}>
|
||||
</paper-textarea>
|
||||
<div class="description-input layout horizontal end">
|
||||
<paper-textarea label="Description" id="containerDescriptionInput" value={{description}}>
|
||||
</paper-textarea>
|
||||
{{> textareaBracketSuffix}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="containerView">
|
||||
|
||||
@@ -54,4 +54,8 @@ Template.containerEdit.events({
|
||||
trimStrings: false,
|
||||
});
|
||||
},
|
||||
"change #carriedToggle": function(event, instance){
|
||||
var carried = !this.isCarried;
|
||||
Containers.update(this._id, {$set: {isCarried: carried}});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,16 +28,16 @@
|
||||
<div class="bottom green" style="padding: 0;">
|
||||
{{> carryCapacityBar}}
|
||||
</div>
|
||||
{{#if encumberedBuffs.count}}
|
||||
{{#if encumberedConditions.count}}
|
||||
<div class="bottom list">
|
||||
{{#each encumberedBuffs}}
|
||||
{{#each condition in encumberedConditions}}
|
||||
<div class="item-slot">
|
||||
<div class="item buff layout horizontal center">
|
||||
<div class="item condition layout horizontal center">
|
||||
<div class="flex">
|
||||
<iron-icon icon="work"
|
||||
style="margin-right: 16px">
|
||||
</iron-icon>
|
||||
{{name}}
|
||||
{{condition.name}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,12 +110,12 @@
|
||||
<div class="paper-font-caption" style="margin-right: 8px">
|
||||
{{round totalWeight}} lbs
|
||||
</div>
|
||||
<div>
|
||||
<div style="position: relative;">
|
||||
<paper-checkbox class="carriedCheckbox"
|
||||
disabled={{#unless canEditCharacter charId}}true{{/unless}}
|
||||
checked={{isCarried}}>
|
||||
</paper-checkbox>
|
||||
<paper-tooltip position="left"> Container carried</paper-tooltip>
|
||||
{{#simpleTooltip}} Container carried{{/simpleTooltip}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom list">
|
||||
@@ -136,21 +136,21 @@
|
||||
class="addContainer"
|
||||
mini>
|
||||
</paper-fab>
|
||||
<paper-tooltip position="left"> New container </paper-tooltip>
|
||||
{{#simpleTooltip class="always"}} Container {{/simpleTooltip}}
|
||||
</div>
|
||||
<div>
|
||||
<paper-fab icon="av:library-books"
|
||||
class="libraryItem"
|
||||
mini>
|
||||
</paper-fab>
|
||||
<paper-tooltip position="left"> Library item </paper-tooltip>
|
||||
{{#simpleTooltip class="always"}} Item from library {{/simpleTooltip}}
|
||||
</div>
|
||||
<div>
|
||||
<paper-fab icon="note-add"
|
||||
class="addItem"
|
||||
mini>
|
||||
</paper-fab>
|
||||
<paper-tooltip position="left"> New item </paper-tooltip>
|
||||
{{#simpleTooltip class="always"}} Item {{/simpleTooltip}}
|
||||
</div>
|
||||
{{/fabMenu}}
|
||||
{{/if}}
|
||||
|
||||
@@ -68,9 +68,8 @@ Template.inventory.helpers({
|
||||
return weight;
|
||||
},
|
||||
encumberedBuffs: function(){
|
||||
return Buffs.find({
|
||||
return Conditions.find({
|
||||
charId: this._id,
|
||||
type: "inate",
|
||||
name: {$in: [
|
||||
"Encumbered",
|
||||
"Heavily encumbered",
|
||||
@@ -157,7 +156,7 @@ Template.inventory.events({
|
||||
return;
|
||||
}
|
||||
// Make the library item into a regular item
|
||||
let item = _.omit(result, "library", "attacks", "effects");
|
||||
let item = _.omit(result, "libraryName", "library", "attacks", "effects");
|
||||
delete item.settings.category;
|
||||
// Update the item to match library item
|
||||
Items.update(itemId, {$set: item});
|
||||
@@ -201,12 +200,10 @@ Template.inventory.events({
|
||||
element: event.currentTarget.parentElement,
|
||||
});
|
||||
},
|
||||
"click .buff": function(event, instance){
|
||||
var buffId = this._id;
|
||||
var charId = Template.parentData()._id;
|
||||
"click .condition": function(event, instance){
|
||||
pushDialogStack({
|
||||
template: "buffDialog",
|
||||
data: {buffId: buffId, charId: charId},
|
||||
template: "conditionViewDialogDialog",
|
||||
data: {condition: this.condition},
|
||||
element: event.currentTarget,
|
||||
});
|
||||
},
|
||||
@@ -334,21 +331,23 @@ Template.layout.events({
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .characterRepresentative": function(event, instance) {
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
pushDialogStack({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: itemId,
|
||||
parentCollection: "Characters",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the character
|
||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
pushDialogStack({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: itemId,
|
||||
parentCollection: "Characters",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the character
|
||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||
}
|
||||
Session.set("inventory.dragItemId", null);
|
||||
}
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
{{/if}}
|
||||
{{> effectsViewList charId=charId parentId=_id}}
|
||||
{{> attacksViewList charId=charId parentId=_id}}
|
||||
{{> customBuffViewList charId=charId parentId=_id}}
|
||||
</template>
|
||||
|
||||
<template name="itemEdit">
|
||||
@@ -61,16 +62,17 @@
|
||||
</div>
|
||||
|
||||
<!--Description-->
|
||||
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}>
|
||||
<div suffix>
|
||||
<paper-tooltip position="left" animation-delay="0">This field accepts formulae in {curly brackets}</paper-tooltip>
|
||||
<iron-icon icon="dicecloud:code-braces"></iron-icon>
|
||||
</div>
|
||||
</paper-textarea>
|
||||
<div class="description-input layout horizontal end">
|
||||
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}></paper-textarea>
|
||||
{{> textareaBracketSuffix}}
|
||||
</div>
|
||||
|
||||
<!--Effects-->
|
||||
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
||||
<!--Attacks-->
|
||||
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
||||
<!-- Buffs -->
|
||||
{{> customBuffEditList parentId=_id parentCollection="Items" charId=charId}}
|
||||
</template>
|
||||
|
||||
<template name="containerDropdown">
|
||||
|
||||
@@ -61,15 +61,15 @@
|
||||
<template name="libraryItem">
|
||||
<tr class="item library-item {{#if selected}}selected{{/if}}">
|
||||
<td class="itemName">
|
||||
{{item.name}}
|
||||
{{itemName}}
|
||||
<paper-ripple></paper-ripple>
|
||||
</td>
|
||||
<td>
|
||||
{{item.weight}} lb.
|
||||
{{itemWeight}} lb.
|
||||
<paper-ripple></paper-ripple>
|
||||
</td>
|
||||
<td>
|
||||
{{valueString item.value}}
|
||||
{{valueString itemValue}}
|
||||
<paper-ripple></paper-ripple>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -107,3 +107,24 @@ Template.itemLibraryDialog.events({
|
||||
template.searchTerm.set(value);
|
||||
},
|
||||
});
|
||||
|
||||
Template.libraryItem.helpers({
|
||||
itemName: function(){
|
||||
return this.item.libraryName || this.item.name;
|
||||
},
|
||||
itemWeight: function(){
|
||||
if (this.item.quantity) {
|
||||
return this.item.weight * this.item.quantity;
|
||||
} else {
|
||||
return this.item.weight;
|
||||
}
|
||||
},
|
||||
itemValue: function(){
|
||||
if (this.item.quantity) {
|
||||
return this.item.value * this.item.quantity;
|
||||
} else {
|
||||
return this.item.value;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</div>
|
||||
<div class="bottom list">
|
||||
<div class="item-slot">
|
||||
<div class="item race layout horizontal center">
|
||||
<div class="item race layout horizontal center {{#if shouldRaceBounce}}bounce{{/if}}">
|
||||
{{race}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,9 +83,12 @@
|
||||
</div>
|
||||
<div class="fab-buffer"></div>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab id="addNote"
|
||||
class="floatyButton"
|
||||
icon="add"
|
||||
title="Add"></paper-fab>
|
||||
<div class="floatyButton">
|
||||
<paper-fab id="addNote"
|
||||
icon="add"
|
||||
title="Add">
|
||||
</paper-fab>
|
||||
{{#simpleTooltip}}Add Note{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
|
||||
@@ -50,6 +50,9 @@ Template.journal.helpers({
|
||||
var char = Characters.findOne(this._id, {fields: {race: 1}});
|
||||
return char && char.race;
|
||||
},
|
||||
shouldRaceBounce: function(){
|
||||
return Session.get("newUserExperienceStep") === 1;
|
||||
},
|
||||
});
|
||||
|
||||
Template.journal.events({
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
<template name="raceDialog">
|
||||
{{#baseDialog title="Race" class=color hideColor="true" hideDelete="true" startEditing=startEditing}}
|
||||
{{#if showNewUserExperience}}
|
||||
{{#infoBox}}
|
||||
{{#if stepComplete}}
|
||||
<p>You can add all the effects you need to represent how your race affects your character's attributes.</p>
|
||||
{{else}}
|
||||
<p>Click the edit button to edit your race and add a racial effect</p>
|
||||
{{/if}}
|
||||
{{/infoBox}}
|
||||
{{/if}}
|
||||
<div class="horizontal layout center-justified paper-font-display2">
|
||||
{{race}}
|
||||
</div>
|
||||
{{> effectsViewList charId=charId parentId=charId parentGroup="racial"}}
|
||||
{{> proficiencyViewList charId=charId parentId=charId parentGroup="racial"}}
|
||||
{{else}}
|
||||
{{#if showNewUserExperience}}
|
||||
{{#infoBox}}
|
||||
{{#if stepComplete}}
|
||||
<p>You can add all the effects you need to represent how your race affects your character's attributes.</p>
|
||||
{{else}}
|
||||
<p>
|
||||
Add an effect with the following options: <br>
|
||||
Attribute: <b>stats > speed</b> <br>
|
||||
Operation: <b>Base Value</b> <br>
|
||||
Value: <b>30</b> (might be different for some races)
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/infoBox}}
|
||||
{{/if}}
|
||||
<paper-input id="raceInput" label="Race" value={{race}}></paper-input>
|
||||
{{> effectsEditList parentId=charId parentCollection="Characters" charId=charId parentGroup="racial"}}
|
||||
{{> proficiencyEditList parentId=charId parentCollection="Characters" charId=charId parentGroup="racial"}}
|
||||
|
||||
@@ -19,4 +19,10 @@ Template.raceDialog.helpers({
|
||||
var char = Characters.findOne(this.charId, {fields: {color: 1}});
|
||||
if (char) return getColorClass(char.color);
|
||||
},
|
||||
stepComplete: function(){
|
||||
return Session.get("newUserExperienceStep") > 1;
|
||||
},
|
||||
showNewUserExperience: function(){
|
||||
return Session.get("newUserExperienceStep") >= 1;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.newUserStepper {
|
||||
height: 180px !important;
|
||||
}
|
||||
|
||||
.newUserStepper paper-step .invalid-step-message {
|
||||
color: #d13b2e;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.newUserStepper paper-step[invalid] .invalid-step-message {
|
||||
visibility: visible;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<template name="newUserStepper">
|
||||
<paper-stepper linear selected="0" class="newUserStepper">
|
||||
<paper-step id="step0" label="Add a feature">
|
||||
<p>
|
||||
To get started, add a feature
|
||||
</p>
|
||||
</paper-step>
|
||||
<paper-step id="step1" label="Add an effect">
|
||||
<p>
|
||||
Add a racial effect to set your speed
|
||||
</p>
|
||||
</paper-step>
|
||||
<paper-step id="step2" label="See the effect in action">
|
||||
<p>
|
||||
View your speed stat
|
||||
</p>
|
||||
</paper-step>
|
||||
<paper-step id="step3" label="Finish">
|
||||
Done! If you get stuck, be sure to check out the <a href="/guide">guide</a>, or ask for help using the feedback form
|
||||
<div class="layout vertical end">
|
||||
<paper-button class="done-button" style="color: #d13b2e">Finish</paper-button>
|
||||
</div>
|
||||
</paper-step>
|
||||
</paper-stepper>
|
||||
</template>
|
||||
|
||||
<template name="newUserStepperPlaceholder">
|
||||
<div style="height: 300px"></div>
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
Template.newUserStepper.onRendered(function(){
|
||||
Session.set("newUserExperienceStep", 0);
|
||||
let stepper = this.find("paper-stepper");
|
||||
_.defer(() => {
|
||||
this.autorun((c) => {
|
||||
var step = Session.get("newUserExperienceStep");
|
||||
var hasFeatures = Features.find({charId: this.data._id}).count() > 1;
|
||||
if (step === 0 && hasFeatures){
|
||||
stepper.continue();
|
||||
}
|
||||
});
|
||||
this.autorun((c) => {
|
||||
var step = Session.get("newUserExperienceStep");
|
||||
var hasEffect = !!Effects.find({
|
||||
charId: this.data._id,
|
||||
stat: "speed",
|
||||
"parent.group": "racial",
|
||||
operation: "base",
|
||||
value: {$gt: 0},
|
||||
}).count();
|
||||
if (step === 1 && hasEffect){
|
||||
stepper.continue();
|
||||
}
|
||||
});
|
||||
this.autorun((c) => {
|
||||
var step = Session.get("newUserExperienceStep");
|
||||
if (step === 2 && Session.get("viewedSpeed")){
|
||||
Session.set("viewedSpeed", undefined);
|
||||
stepper.continue();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Template.newUserStepper.events({
|
||||
"paper-stepper-progressed paper-stepper": function(event, template){
|
||||
const step = template.find("paper-stepper").selected;
|
||||
Session.set("newUserExperienceStep", step);
|
||||
},
|
||||
"paper-stepper-completed paper-stepper": function(event, template){
|
||||
Session.set("newUserExperienceStep", undefined);
|
||||
Session.set("showNewUserExperience", undefined);
|
||||
Characters.update(this._id, {$unset: {"settings.newUserExperience": 1}});
|
||||
},
|
||||
"click .done-button": function(event, instance){
|
||||
const stepper = instance.find("paper-stepper");
|
||||
stepper.continue();
|
||||
},
|
||||
});
|
||||
|
||||
Template.stats.events({
|
||||
"click .stat-card": function(event, instance){
|
||||
var step = Session.get("newUserExperienceStep");
|
||||
if (this.stat === "speed" && step === 2){
|
||||
Session.set("viewedSpeed", true);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -7,6 +7,24 @@ var colorMap = {
|
||||
backstory: "j",
|
||||
};
|
||||
|
||||
var removeDuplicateProficiencies = function(proficiencies) {
|
||||
dict = {};
|
||||
proficiencies.forEach(function(prof) {
|
||||
if (prof.name in dict) { //if we have already gone over another proficiency for the same thing
|
||||
if (dict[prof.name].value < prof.value) {
|
||||
dict[prof.name] = prof; //then take the new one if it's higher, otherwise leave it
|
||||
}
|
||||
} else {
|
||||
dict[prof.name] = prof; //if it wasn't already there, store it
|
||||
}
|
||||
});
|
||||
profs = []
|
||||
_.forEach(dict, function(prof) {
|
||||
profs.push(prof);
|
||||
})
|
||||
return profs;
|
||||
};
|
||||
|
||||
Template.persona.helpers({
|
||||
characterDetails: function(){
|
||||
var char = Characters.findOne(
|
||||
@@ -33,7 +51,8 @@ Template.persona.helpers({
|
||||
};
|
||||
},
|
||||
languages: function(){
|
||||
return Proficiencies.find({charId: this._id, type: "language"});
|
||||
var profs = Proficiencies.find({charId: this._id, type: "language"});
|
||||
return removeDuplicateProficiencies(profs);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,22 @@ Template.proficiencyListItem.helpers({
|
||||
|
||||
Template.proficiencyListItem.events({
|
||||
"click .proficiency": function(event, instance){
|
||||
if (this.parent.collection == "Characters") {
|
||||
if (this.parent.group == "background") {
|
||||
pushDialogStack({
|
||||
template: "backgroundDialog",
|
||||
data: {
|
||||
"charId": this.charId,
|
||||
"field":"background",
|
||||
"title":"Background",
|
||||
"color":"j",
|
||||
},
|
||||
element: event.currentTarget,
|
||||
})
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
openParentDialog({
|
||||
parent: this.parent,
|
||||
charId: this.charId,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<template name="spellDetails">
|
||||
<div class="paper-font-body2">
|
||||
Level {{level}} {{school}} {{#if ritual}}ritual{{/if}}, {{preparedString}}
|
||||
{{schoolAndLevel}}{{#if ritual}} (ritual){{/if}}, {{preparedString}}
|
||||
</div>
|
||||
<div style="margin: 16px 0 16px 0;">
|
||||
{{#if castingTime}}
|
||||
@@ -36,8 +36,9 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
|
||||
<div>{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}</div>
|
||||
{{> attacksViewList charId=charId parentId=_id}}
|
||||
{{> customBuffViewList charId=charId parentId=_id}}
|
||||
</template>
|
||||
|
||||
<template name="spellEdit">
|
||||
@@ -112,7 +113,11 @@
|
||||
</div>
|
||||
|
||||
<!--Description-->
|
||||
<paper-textarea id="descriptionInput" label="Description" value="{{description}}">
|
||||
</paper-textarea>
|
||||
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name}}
|
||||
<div class="description-input layout horizontal end">
|
||||
<paper-textarea id="descriptionInput" label="Description" style="width: calc(100% - 24px)" value={{description}}></paper-textarea>
|
||||
{{> textareaBracketSuffix}}
|
||||
</div>
|
||||
|
||||
{{> customBuffEditList parentId=_id parentCollection="Spells" charId=charId}}
|
||||
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name isSpell=true}}
|
||||
</template>
|
||||
|
||||
@@ -29,6 +29,13 @@ Template.spellDialog.events({
|
||||
});
|
||||
|
||||
Template.spellDetails.helpers({
|
||||
schoolAndLevel: function(){
|
||||
if (this.level == 0) {
|
||||
return this.school + " cantrip";
|
||||
} else {
|
||||
return "Level " + this.level + " " + this.school;
|
||||
}
|
||||
},
|
||||
getComponents: function(){
|
||||
var components = "";
|
||||
if (this.components.concentration) components += "C";
|
||||
|
||||
@@ -40,8 +40,11 @@
|
||||
{{> formulaSuffix}}
|
||||
</paper-input>
|
||||
<!--Description-->
|
||||
<paper-textarea id="spellListDescriptionInput" label="Description" value={{description}}>
|
||||
</paper-textarea>
|
||||
<div class="description-input layout horizontal end">
|
||||
<paper-textarea id="spellListDescriptionInput" label="Description" value={{description}}>
|
||||
</paper-textarea>
|
||||
{{> textareaBracketSuffix}}
|
||||
</div>
|
||||
</div>
|
||||
{{/baseDialog}}
|
||||
{{/with}}
|
||||
|
||||
@@ -78,6 +78,20 @@
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.spell.item > div > div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.spell.item > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.spell.item iron-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.spellLevel {
|
||||
-webkit-backface-visibility: hidden;
|
||||
-webkit-transform: translateX(0);
|
||||
|
||||
@@ -53,22 +53,22 @@
|
||||
{{numPrepared}} / {{evaluate charId maxPrepared}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div>
|
||||
<paper-tooltip position="left">
|
||||
Done
|
||||
</paper-tooltip>
|
||||
<div style="position: relative;">
|
||||
<paper-icon-button class="finishPrep" icon="done">
|
||||
</paper-icon-button>
|
||||
{{#simpleTooltip}}
|
||||
Done
|
||||
{{/simpleTooltip}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div>
|
||||
<paper-tooltip position="left">
|
||||
Change prepared spells
|
||||
</paper-tooltip>
|
||||
<div style="position: relative;">
|
||||
<paper-icon-button class="prepSpells"
|
||||
disabled={{#unless canEditCharacter charId}}true{{/unless}}
|
||||
icon="book">
|
||||
</paper-icon-button>
|
||||
{{#simpleTooltip}}
|
||||
Change prepared spells
|
||||
{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
@@ -83,7 +83,9 @@
|
||||
{{#each spells ../_id ../../_id}}
|
||||
{{#if showSpell ../../_id}}
|
||||
<div class="item-slot">
|
||||
<div class="tall spell item layout horizontal center" data-id={{_id}}>
|
||||
<div class="tall spell item layout horizontal center spellItem"
|
||||
data-id={{_id}}
|
||||
draggable={{canEditCharacter charId}}>
|
||||
<iron-icon icon="social:whatshot"
|
||||
style="color: {{hexColor color}};
|
||||
margin-right: 16px;"
|
||||
@@ -122,32 +124,31 @@
|
||||
{{#if canEditCharacter _id}}
|
||||
{{#fabMenu}}
|
||||
<div>
|
||||
<paper-tooltip position="left">
|
||||
New spell list
|
||||
</paper-tooltip>
|
||||
<paper-fab icon="work"
|
||||
class="addSpellList"
|
||||
mini>
|
||||
</paper-fab>
|
||||
|
||||
{{#simpleTooltip class="always"}}
|
||||
Spell list
|
||||
{{/simpleTooltip}}
|
||||
</div>
|
||||
<div>
|
||||
<paper-tooltip position="left">
|
||||
Spell library
|
||||
</paper-tooltip>
|
||||
<paper-fab icon="av:library-books"
|
||||
class="librarySpell"
|
||||
mini>
|
||||
</paper-fab>
|
||||
{{#simpleTooltip class="always"}}
|
||||
Spell from library
|
||||
{{/simpleTooltip}}
|
||||
</div>
|
||||
<div>
|
||||
<paper-tooltip position="left">
|
||||
New spell
|
||||
</paper-tooltip>
|
||||
<paper-fab icon="note-add"
|
||||
class="addSpell"
|
||||
mini>
|
||||
</paper-fab>
|
||||
{{#simpleTooltip class="always"}}
|
||||
Spell
|
||||
{{/simpleTooltip}}
|
||||
</div>
|
||||
{{/fabMenu}}
|
||||
{{/if}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user