Compare commits
132 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -21,32 +21,40 @@
|
|||||||
"weight": 1
|
"weight": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Arrows (20)",
|
"libraryName": "Arrows (20)",
|
||||||
"plural": "Arrows (20)",
|
"name": "Arrow",
|
||||||
|
"plural": "Arrows",
|
||||||
"description": "",
|
"description": "",
|
||||||
"value": 1,
|
"value": 0.05,
|
||||||
"weight": 1
|
"weight": 0.05,
|
||||||
|
"quantity": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Blowgun needles (5)",
|
"libraryName": "Blowgun needles (5)",
|
||||||
"plural": "Blowgun needles (5)",
|
"name": "Blowgun needle",
|
||||||
|
"plural": "Blowgun needles",
|
||||||
"description": "",
|
"description": "",
|
||||||
"value": 1,
|
"value": 0.2,
|
||||||
"weight": 1
|
"weight": 0.2,
|
||||||
|
"quantity": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Crossbow bolts (20)",
|
"libraryName": "Crossbow bolts (20)",
|
||||||
"plural": "Crossbow bolts (20)",
|
"name": "Crossbow bolt",
|
||||||
|
"plural": "Crossbow bolts",
|
||||||
"description": "",
|
"description": "",
|
||||||
"value": 1,
|
"value": 0.05,
|
||||||
"weight": 1.5
|
"weight": 0.075,
|
||||||
|
"quantity": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sling bullets (20)",
|
"libraryName": "Sling bullets (20)",
|
||||||
"plural": "Sling bullets (20)",
|
"name": "Sling bullet",
|
||||||
|
"plural": "Sling bullets",
|
||||||
"description": "",
|
"description": "",
|
||||||
"value": 0.04,
|
"value": 0.002,
|
||||||
"weight": 1.5
|
"weight": 0.075,
|
||||||
|
"quantity": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Antitoxin (vial)",
|
"name": "Antitoxin (vial)",
|
||||||
@@ -651,11 +659,13 @@
|
|||||||
"weight": 3
|
"weight": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Spikes, iron (10)",
|
"libraryName": "Spikes, iron (10)",
|
||||||
"plural": "Spikes, iron (10)",
|
"name": "Spike, iron",
|
||||||
|
"plural": "Spikes, iron",
|
||||||
"description": "",
|
"description": "",
|
||||||
"value": 1,
|
"value": 0.1,
|
||||||
"weight": 5
|
"weight": 0.5,
|
||||||
|
"quantity": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Spyglass",
|
"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
|
// Only do if the library doesn't exist yet
|
||||||
id = Libraries.insert({
|
id = Libraries.insert({
|
||||||
_id: "SRDLibraryGA3XWsd",
|
_id: "SRDLibraryGA3XWsd",
|
||||||
@@ -5,6 +7,8 @@ id = Libraries.insert({
|
|||||||
name: "SRD Library",
|
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) => {
|
_.each(items, (item) => {
|
||||||
item.settings = {category: }; // "adventuringGear", "armor", "weapons", "tools"
|
item.settings = {category: }; // "adventuringGear", "armor", "weapons", "tools"
|
||||||
item.library = "SRDLibraryGA3XWsd"
|
item.library = "SRDLibraryGA3XWsd"
|
||||||
@@ -15,3 +19,38 @@ _.each(spells, (spell) => {
|
|||||||
spell.library = "SRDLibraryGA3XWsd"
|
spell.library = "SRDLibraryGA3XWsd"
|
||||||
LibrarySpells.insert(spell)
|
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});
|
||||||
|
});
|
||||||
|
|||||||
@@ -48,3 +48,5 @@ es5-shim@4.6.15
|
|||||||
differential:vulcanize
|
differential:vulcanize
|
||||||
reactive-dict
|
reactive-dict
|
||||||
percolate:synced-cron
|
percolate:synced-cron
|
||||||
|
ongoworks:speakingurl
|
||||||
|
service-configuration
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ npm-mongo@2.2.16_1
|
|||||||
oauth@1.1.12
|
oauth@1.1.12
|
||||||
oauth2@1.1.11
|
oauth2@1.1.11
|
||||||
observe-sequence@1.0.14
|
observe-sequence@1.0.14
|
||||||
|
ongoworks:speakingurl@9.0.0
|
||||||
ordered-dict@1.0.9
|
ordered-dict@1.0.9
|
||||||
percolate:migrations@0.9.8
|
percolate:migrations@0.9.8
|
||||||
percolate:synced-cron@1.3.2
|
percolate:synced-cron@1.3.2
|
||||||
|
|||||||
@@ -1,8 +1,42 @@
|
|||||||
Parties = new Mongo.Collection("parties");
|
Parties = new Mongo.Collection("parties");
|
||||||
|
|
||||||
Schemas.Party = new SimpleSchema({
|
Schemas.Party = new SimpleSchema({
|
||||||
//each character/monster can only be in one party at a time
|
name: {
|
||||||
//each party can only be in a single instance at a time
|
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.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: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ Schemas.Attack = new SimpleSchema({
|
|||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "New Attack",
|
defaultValue: "New Attack",
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Schemas.Buff = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
@@ -22,7 +23,7 @@ Schemas.Buff = new SimpleSchema({
|
|||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
allowedValues: [
|
allowedValues: [
|
||||||
"inate",
|
"inate", //this should be "innate", but changing it could be problematic
|
||||||
"custom",
|
"custom",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -41,12 +42,26 @@ Schemas.Buff = new SimpleSchema({
|
|||||||
allowedValues: _.pluck(colorOptions, "key"),
|
allowedValues: _.pluck(colorOptions, "key"),
|
||||||
defaultValue: "q",
|
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.attachSchema(Schemas.Buff);
|
||||||
|
|
||||||
Buffs.attachBehaviour("softRemovable");
|
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.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||||
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);
|
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Characters = new Mongo.Collection("characters");
|
|||||||
Schemas.Character = new SimpleSchema({
|
Schemas.Character = new SimpleSchema({
|
||||||
//strings
|
//strings
|
||||||
name: {type: String, defaultValue: "", trim: false, optional: true},
|
name: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
|
urlName: {type: String, defaultValue: "-", trim: false, optional: true},
|
||||||
alignment: {type: String, defaultValue: "", trim: false, optional: true},
|
alignment: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
gender: {type: String, defaultValue: "", trim: false, optional: true},
|
gender: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
race: {type: String, defaultValue: "", trim: false, optional: true},
|
race: {type: String, defaultValue: "", trim: false, optional: true},
|
||||||
@@ -26,6 +27,7 @@ Schemas.Character = new SimpleSchema({
|
|||||||
|
|
||||||
//stats
|
//stats
|
||||||
hitPoints: {type: Schemas.Attribute},
|
hitPoints: {type: Schemas.Attribute},
|
||||||
|
tempHP: {type: Schemas.Attribute},
|
||||||
experience: {type: Schemas.Attribute},
|
experience: {type: Schemas.Attribute},
|
||||||
proficiencyBonus: {type: Schemas.Attribute},
|
proficiencyBonus: {type: Schemas.Attribute},
|
||||||
speed: {type: Schemas.Attribute},
|
speed: {type: Schemas.Attribute},
|
||||||
@@ -184,6 +186,7 @@ Schemas.Character = new SimpleSchema({
|
|||||||
defaultValue: "whitelist",
|
defaultValue: "whitelist",
|
||||||
allowedValues: ["whitelist", "public"],
|
allowedValues: ["whitelist", "public"],
|
||||||
},
|
},
|
||||||
|
"settings.swapStatAndModifier": {type: Boolean, defaultValue: false},
|
||||||
"settings.exportFeatures": {type: Boolean, defaultValue: true},
|
"settings.exportFeatures": {type: Boolean, defaultValue: true},
|
||||||
"settings.exportAttacks": {type: Boolean, defaultValue: true},
|
"settings.exportAttacks": {type: Boolean, defaultValue: true},
|
||||||
"settings.exportDescription": {type: Boolean, defaultValue: true},
|
"settings.exportDescription": {type: Boolean, defaultValue: true},
|
||||||
@@ -257,7 +260,10 @@ var attributeBase = preventLoop(function(charId, statName){
|
|||||||
var result = (base + add) * mul;
|
var result = (base + add) * mul;
|
||||||
if (result < min) result = min;
|
if (result < min) result = min;
|
||||||
if (result > max) result = max;
|
if (result > max) result = max;
|
||||||
|
// Don't round carry multiplier
|
||||||
|
if (statName === "carryMultiplier"){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
return Math.floor(result);
|
return Math.floor(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -277,6 +283,7 @@ if (Meteor.isClient) {
|
|||||||
|
|
||||||
//create a local memoize with a argument concatenating hash function
|
//create a local memoize with a argument concatenating hash function
|
||||||
var memoize = function(f) {
|
var memoize = function(f) {
|
||||||
|
if (Meteor.isServer) return f;
|
||||||
return Tracker.memoize(f, function() {
|
return Tracker.memoize(f, function() {
|
||||||
return _.reduce(arguments, function(memo, arg) {
|
return _.reduce(arguments, function(memo, arg) {
|
||||||
return memo + arg;
|
return memo + arg;
|
||||||
@@ -528,6 +535,7 @@ if (Meteor.isServer){
|
|||||||
Attacks .remove({charId: character._id});
|
Attacks .remove({charId: character._id});
|
||||||
Buffs .remove({charId: character._id});
|
Buffs .remove({charId: character._id});
|
||||||
Classes .remove({charId: character._id});
|
Classes .remove({charId: character._id});
|
||||||
|
CustomBuffs .remove({charId: character._id});
|
||||||
Effects .remove({charId: character._id});
|
Effects .remove({charId: character._id});
|
||||||
Experiences .remove({charId: character._id});
|
Experiences .remove({charId: character._id});
|
||||||
Features .remove({charId: character._id});
|
Features .remove({charId: character._id});
|
||||||
@@ -537,6 +545,15 @@ if (Meteor.isServer){
|
|||||||
Items .remove({charId: character._id});
|
Items .remove({charId: character._id});
|
||||||
Containers .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}) || "-";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Characters.allow({
|
Characters.allow({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Classes = new Mongo.Collection("classes");
|
|||||||
|
|
||||||
Schemas.Class = new SimpleSchema({
|
Schemas.Class = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
name: {type: String, trim: false},
|
name: {type: String, optional: true, trim: false},
|
||||||
level: {type: Number},
|
level: {type: Number},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: Date,
|
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({
|
Schemas.Experience = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
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},
|
description: {type: String, optional: true, trim: false},
|
||||||
value: {type: Number, defaultValue: 0},
|
value: {type: Number, defaultValue: 0},
|
||||||
dateAdded: {
|
dateAdded: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Features = new Mongo.Collection("features");
|
|||||||
|
|
||||||
Schemas.Feature = new SimpleSchema({
|
Schemas.Feature = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
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},
|
description: {type: String, optional: true, trim: false},
|
||||||
uses: {type: String, optional: true, trim: false},
|
uses: {type: String, optional: true, trim: false},
|
||||||
used: {type: Number, defaultValue: 0},
|
used: {type: Number, defaultValue: 0},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Notes = new Mongo.Collection("notes");
|
|||||||
|
|
||||||
Schemas.Note = new SimpleSchema({
|
Schemas.Note = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
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},
|
description: {type: String, optional: true, trim: false},
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ SpellLists = new Mongo.Collection("spellLists");
|
|||||||
|
|
||||||
Schemas.SpellLists = new SimpleSchema({
|
Schemas.SpellLists = new SimpleSchema({
|
||||||
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
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},
|
description: {type: String, optional: true, trim: false},
|
||||||
saveDC: {type: String, optional: true, trim: false},
|
saveDC: {type: String, optional: true, trim: false},
|
||||||
attackBonus: {type: String, optional: true, trim: false},
|
attackBonus: {type: String, optional: true, trim: false},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Schemas.Spell = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
defaultValue: "New Spell",
|
defaultValue: "New Spell",
|
||||||
},
|
},
|
||||||
@@ -82,3 +83,170 @@ Spells.after.update(function (userId, spell, fieldNames) {
|
|||||||
|
|
||||||
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
|
||||||
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
|
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");
|
Containers = new Mongo.Collection("containers");
|
||||||
|
|
||||||
Schemas.Container = new SimpleSchema({
|
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},
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1},
|
||||||
isCarried: {type: Boolean},
|
isCarried: {type: Boolean},
|
||||||
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
weight: {type: Number, min: 0, defaultValue: 0, decimal: true},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Items = new Mongo.Collection("items");
|
Items = new Mongo.Collection("items");
|
||||||
|
|
||||||
Schemas.Item = new SimpleSchema({
|
Schemas.Item = new SimpleSchema({
|
||||||
name: {type: String, defaultValue: "New Item", trim: false},
|
name: {type: String, optional: true, trim: false, defaultValue: "New Item"},
|
||||||
plural: {type: String, optional: true, trim: false},
|
plural: {type: String, optional: true, trim: false},
|
||||||
description:{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
|
charId: {type: String, regEx: SimpleSchema.RegEx.Id, index: 1}, //id of owner
|
||||||
|
|||||||
@@ -11,13 +11,11 @@ Schemas.LibraryAttacks = new SimpleSchema({
|
|||||||
},
|
},
|
||||||
attackBonus: {
|
attackBonus: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "strengthMod + proficiencyBonus",
|
|
||||||
optional: true,
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: "1d8 + {strengthMod}",
|
|
||||||
optional: true,
|
optional: true,
|
||||||
trim: false,
|
trim: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
LibraryItems = new Mongo.Collection("libraryItems");
|
LibraryItems = new Mongo.Collection("libraryItems");
|
||||||
|
|
||||||
Schemas.LibraryItems = new SimpleSchema({
|
Schemas.LibraryItems = new SimpleSchema({
|
||||||
|
libraryName:{type: String, optional: true, trim: false},
|
||||||
name: {type: String, defaultValue: "New Item", trim: false},
|
name: {type: String, defaultValue: "New Item", trim: false},
|
||||||
plural: {type: String, optional: true, trim: false},
|
plural: {type: String, optional: true, trim: false},
|
||||||
description:{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", {
|
this.route("characterList", {
|
||||||
path: "/characterList",
|
path: "/characterList",
|
||||||
waitOn: function(){
|
waitOn: function(){
|
||||||
return subsManager.subscribe("characterList", Meteor.userId());
|
return subsManager.subscribe("characterList");
|
||||||
},
|
},
|
||||||
onAfterAction: function() {
|
onAfterAction: function() {
|
||||||
document.title = appName + " - Characters";
|
document.title = appName + " - Characters";
|
||||||
@@ -32,11 +32,27 @@ Router.map(function() {
|
|||||||
fastRender: true,
|
fastRender: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route("characterSheet", {
|
this.route("characterSheetNaked", {
|
||||||
path: "/character/:_id",
|
path: "/character/:_id/",
|
||||||
waitOn: function(){
|
waitOn: function(){
|
||||||
return [
|
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() {
|
data: function() {
|
||||||
|
|||||||
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);
|
return evaluateString(charId, string);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
|
||||||
|
return evaluateSpellString(charId, spellListId, string);
|
||||||
|
});
|
||||||
|
|
||||||
Template.registerHelper("evaluateShortString", function(charId, string) {
|
Template.registerHelper("evaluateShortString", function(charId, string) {
|
||||||
if (_.isString(string)){
|
if (_.isString(string)){
|
||||||
return evaluateString(
|
return evaluateString(
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ openParentDialog = function({
|
|||||||
} else if (parent.collection === "Spells") {
|
} else if (parent.collection === "Spells") {
|
||||||
template = "spellDialog";
|
template = "spellDialog";
|
||||||
data = {spellId: parent.id};
|
data = {spellId: parent.id};
|
||||||
|
} else if (parent.collection === "Buffs") {
|
||||||
|
template = "buffDialog";
|
||||||
|
data = {buffId: parent.id};
|
||||||
}
|
}
|
||||||
pushDialogStack({template, data, element, returnElement, callback});
|
pushDialogStack({template, data, element, returnElement, callback});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,7 +49,10 @@ improvedInitiativeJson = function(charId, options){
|
|||||||
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
|
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
|
||||||
],
|
],
|
||||||
"Skills": getSkills(charId),
|
"Skills": getSkills(charId),
|
||||||
"Senses": [],
|
"Senses": [
|
||||||
|
"passive Perception " +
|
||||||
|
Characters.calculate.passiveSkill(charId, "perception")
|
||||||
|
],
|
||||||
"Languages": getLanguages(charId),
|
"Languages": getLanguages(charId),
|
||||||
"Challenge": "",
|
"Challenge": "",
|
||||||
"Traits": options.features ? getTraits(charId) : [],
|
"Traits": options.features ? getTraits(charId) : [],
|
||||||
@@ -116,10 +119,11 @@ var getDamageMods = function(charId){
|
|||||||
];
|
];
|
||||||
// jscs:enable maximumLineLength
|
// jscs:enable maximumLineLength
|
||||||
multipliers = _.groupBy(multipliers, "value");
|
multipliers = _.groupBy(multipliers, "value");
|
||||||
|
var names = o => o.name;
|
||||||
return {
|
return {
|
||||||
"immunities": multipliers["0"] || [],
|
"immunities": _.map(multipliers["0"], names),
|
||||||
"resistances": multipliers["0.5"] || [],
|
"resistances": _.map(multipliers["0.5"], names),
|
||||||
"weaknesses": multipliers["2"] || [],
|
"vulnerabilities": _.map(multipliers["2"], names),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
};
|
||||||
@@ -7,12 +7,32 @@ Template.attackEditList.helpers({
|
|||||||
|
|
||||||
Template.attackEditList.events({
|
Template.attackEditList.events({
|
||||||
"tap #addAttackButton": function() {
|
"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({
|
Attacks.insert({
|
||||||
charId: this.charId,
|
charId: this.charId,
|
||||||
parent: {
|
parent: {
|
||||||
id: this.parentId,
|
id: this.parentId,
|
||||||
collection: this.parentCollection
|
collection: this.parentCollection
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<template name="attackView">
|
<template name="attackView">
|
||||||
<div class="attackView layout horizontal">
|
<div class="attackView layout horizontal">
|
||||||
<div class="paper-font-headline layout horizontal center" style="margin-right: 16px;">
|
<div class="paper-font-headline layout horizontal center" style="margin-right: 16px;">
|
||||||
{{evaluateSigned charId attackBonus}}
|
{{evaluateAttackBonus charId attack}}
|
||||||
</div>
|
</div>
|
||||||
<div class="layout vertical">
|
<div class="layout vertical">
|
||||||
<div>
|
<div>
|
||||||
{{evaluateString charId damage}} {{damageType}}
|
{{evaluateDamage charId attack}} {{attack.damageType}}
|
||||||
</div>
|
</div>
|
||||||
{{#if details}}
|
{{#if attack.details}}
|
||||||
<div class="paper-font-caption">
|
<div class="paper-font-caption">
|
||||||
{{details}}
|
{{attack.details}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</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;">
|
<hr style="margin: 16px 0 16px 0;">
|
||||||
<div class="attacks">
|
<div class="attacks">
|
||||||
<div class="spaceAfter paper-font-title">Attacks</div>
|
<div class="spaceAfter paper-font-title">Attacks</div>
|
||||||
{{#each attacks}}
|
{{#each attack in attacks}}
|
||||||
{{> attackView}}
|
{{> attackView attack=attack charId=charId}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/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">
|
<template name="buffDialog">
|
||||||
{{#with buff}}
|
{{#with buff}}
|
||||||
{{#baseDialog title=name class=colorClass hideEdit=true}}
|
{{#baseDialog title=name class="white" hideColor=true startEditing=true editOnly=true}}
|
||||||
|
{{> buffDetails}}
|
||||||
|
{{else}}
|
||||||
{{> buffDetails}}
|
{{> buffDetails}}
|
||||||
{{/baseDialog}}
|
{{/baseDialog}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="buffDetails">
|
<template name="buffDetails">
|
||||||
|
<div>
|
||||||
|
{{appliedBy}}
|
||||||
|
</div>
|
||||||
|
<hr style="margin: 16px 0 16px 0;">
|
||||||
{{#if description}}
|
{{#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}}
|
{{/if}}
|
||||||
|
|
||||||
{{> effectsViewList charId=charId parentId=_id}}
|
{{> effectsViewList charId=charId parentId=_id}}
|
||||||
|
{{> proficiencyViewList charId=charId parentId=_id}}
|
||||||
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
</template>
|
</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({
|
Template.buffDialog.helpers({
|
||||||
buff: function(){
|
buff: function(){
|
||||||
return Buffs.findOne(this.buffId);
|
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}}>
|
<paper-toggle-button id="variantEncumbrance" checked={{settings.useVariantEncumbrance}}>
|
||||||
Use variant encumbrance
|
Use variant encumbrance
|
||||||
</paper-toggle-button>
|
</paper-toggle-button>
|
||||||
|
<paper-toggle-button id="swapStatAndModifier" checked={{settings.swapStatAndModifier}}>
|
||||||
|
Swap stats and modifiers on Stats page
|
||||||
|
</paper-toggle-button>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</app-header-layout>
|
||||||
<div class="buttons layout horizontal end-justified">
|
<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){
|
"click .doneButton": function(event, instance){
|
||||||
popDialogStack();
|
popDialogStack();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -30,6 +30,16 @@
|
|||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
</paper-menu>
|
</paper-menu>
|
||||||
</paper-menu-button>
|
</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}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div bottom-item>
|
<div bottom-item>
|
||||||
|
|||||||
@@ -210,4 +210,11 @@ Template.characterSheet.events({
|
|||||||
element: event.currentTarget.parentElement.parentElement,
|
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"
|
label="Value"
|
||||||
floatinglabel
|
floatinglabel
|
||||||
value={{effectValue}}>
|
value={{effectValue}}>
|
||||||
|
{{> formulaSuffix}}
|
||||||
</paper-input>
|
</paper-input>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div style="height: 62px;"></div>
|
<div style="height: 62px;"></div>
|
||||||
@@ -20,26 +21,28 @@
|
|||||||
<div class="effectEdit layout horizontal flex">
|
<div class="effectEdit layout horizontal flex">
|
||||||
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
|
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
|
||||||
{{#each statGroups}}
|
{{#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}}
|
{{this}}
|
||||||
</div>
|
</div>
|
||||||
{{#each stats}}
|
<iron-collapse opened={{isGroupSelected this ../stat}}>
|
||||||
<paper-item name={{stat}}>{{name}}</paper-item>
|
{{#each stats}}
|
||||||
{{/each}}
|
<paper-item name={{stat}} class="clickable">{{name}}</paper-item>
|
||||||
|
{{/each}}
|
||||||
|
</iron-collapse>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</dicecloud-selector>
|
</dicecloud-selector>
|
||||||
{{#if operations}}
|
{{#if operations}}
|
||||||
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
|
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
|
||||||
{{#each operations}}
|
{{#each operations}}
|
||||||
<paper-item name={{operation}}>{{name}}</paper-item>
|
<paper-item name={{operation}} class="clickable">{{name}}</paper-item>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</dicecloud-selector>
|
</dicecloud-selector>
|
||||||
{{else}} {{#if showMultiplierOperations}}
|
{{else}} {{#if showMultiplierOperations}}
|
||||||
<dicecloud-selector class="multiplierMenu flex"
|
<dicecloud-selector class="multiplierMenu flex"
|
||||||
selected={{value}}>
|
selected={{value}}>
|
||||||
<paper-item name="0.5">Resistance</paper-item>
|
<paper-item name="0.5" class="clickable">Resistance</paper-item>
|
||||||
<paper-item name="2">Vulnerability</paper-item>
|
<paper-item name="2" class="clickable">Vulnerability</paper-item>
|
||||||
<paper-item name="0">Immunity</paper-item>
|
<paper-item name="0" class="clickable">Immunity</paper-item>
|
||||||
</dicecloud-selector>
|
</dicecloud-selector>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="flex" style="height: 100%;"></div>
|
<div class="flex" style="height: 100%;"></div>
|
||||||
|
|||||||
@@ -7,32 +7,36 @@ var stats = [
|
|||||||
{stat: "intelligence", name: "Intelligence", group: "Ability Scores"},
|
{stat: "intelligence", name: "Intelligence", group: "Ability Scores"},
|
||||||
{stat: "wisdom", name: "Wisdom", group: "Ability Scores"},
|
{stat: "wisdom", name: "Wisdom", group: "Ability Scores"},
|
||||||
{stat: "charisma", name: "Charisma", 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"},
|
{stat: "strengthSave", name: "Strength Save", group: "Saving Throws"},
|
||||||
{name: "Constitution Save", stat: "constitutionSave", group: "Saving Throws"},
|
{stat: "dexteritySave", name: "Dexterity Save", group: "Saving Throws"},
|
||||||
{name: "Intelligence Save", stat: "intelligenceSave", group: "Saving Throws"},
|
{stat: "constitutionSave", name: "Constitution Save", group: "Saving Throws"},
|
||||||
{name: "Wisdom Save", stat: "wisdomSave", group: "Saving Throws"},
|
{stat: "intelligenceSave", name: "Intelligence Save", group: "Saving Throws"},
|
||||||
{name: "Charisma Save", stat: "charismaSave", group: "Saving Throws"},
|
{stat: "wisdomSave", name: "Wisdom Save", group: "Saving Throws"},
|
||||||
{name: "Acrobatics", stat: "acrobatics", group: "Skills"},
|
{stat: "charismaSave", name: "Charisma Save", group: "Saving Throws"},
|
||||||
{name: "Animal Handling", stat: "animalHandling", group: "Skills"},
|
|
||||||
{name: "Arcana", stat: "arcana", group: "Skills"},
|
{stat: "acrobatics", name: "Acrobatics", group: "Skills"},
|
||||||
{name: "Athletics", stat: "athletics", group: "Skills"},
|
{stat: "animalHandling", name: "Animal Handling", group: "Skills"},
|
||||||
{name: "Deception", stat: "deception", group: "Skills"},
|
{stat: "arcana", name: "Arcana", group: "Skills"},
|
||||||
{name: "History", stat: "history", group: "Skills"},
|
{stat: "athletics", name: "Athletics", group: "Skills"},
|
||||||
{name: "Insight", stat: "insight", group: "Skills"},
|
{stat: "deception", name: "Deception", group: "Skills"},
|
||||||
{name: "Intimidation", stat: "intimidation", group: "Skills"},
|
{stat: "history", name: "History", group: "Skills"},
|
||||||
{name: "Investigation", stat: "investigation", group: "Skills"},
|
{stat: "insight", name: "Insight", group: "Skills"},
|
||||||
{name: "Medicine", stat: "medicine", group: "Skills"},
|
{stat: "intimidation", name: "Intimidation", group: "Skills"},
|
||||||
{name: "Nature", stat: "nature", group: "Skills"},
|
{stat: "investigation", name: "Investigation", group: "Skills"},
|
||||||
{name: "Perception", stat: "perception", group: "Skills"},
|
{stat: "medicine", name: "Medicine", group: "Skills"},
|
||||||
{name: "Performance", stat: "performance", group: "Skills"},
|
{stat: "nature", name: "Nature", group: "Skills"},
|
||||||
{name: "Persuasion", stat: "persuasion", group: "Skills"},
|
{stat: "perception", name: "Perception", group: "Skills"},
|
||||||
{name: "Religion", stat: "religion", group: "Skills"},
|
{stat: "performance", name: "Performance", group: "Skills"},
|
||||||
{name: "Sleight of Hand", stat: "sleightOfHand", group: "Skills"},
|
{stat: "persuasion", name: "Persuasion", group: "Skills"},
|
||||||
{name: "Stealth", stat: "stealth", group: "Skills"},
|
{stat: "religion", name: "Religion", group: "Skills"},
|
||||||
{name: "Survival", stat: "survival", group: "Skills"},
|
{stat: "sleightOfHand", name: "Sleight of Hand", group: "Skills"},
|
||||||
{name: "Initiative", stat: "initiative", 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: "hitPoints", name: "Hit Points", group: "Stats"},
|
||||||
|
{stat: "tempHP", name: "Temporary Hit Points", group: "Stats"},
|
||||||
{stat: "armor", name: "Armor", group: "Stats"},
|
{stat: "armor", name: "Armor", group: "Stats"},
|
||||||
{stat: "dexterityArmor", name: "Dexterity Armor Bonus", group: "Stats"},
|
{stat: "dexterityArmor", name: "Dexterity Armor Bonus", group: "Stats"},
|
||||||
{stat: "speed", name: "Speed", group: "Stats"},
|
{stat: "speed", name: "Speed", group: "Stats"},
|
||||||
@@ -44,6 +48,7 @@ var stats = [
|
|||||||
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
|
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
|
||||||
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
|
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
|
||||||
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
|
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
|
||||||
|
|
||||||
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
|
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
|
||||||
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
|
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
|
||||||
{stat: "level3SpellSlots", name: "level 3", 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: "level7SpellSlots", name: "level 7", group: "Spell Slots"},
|
||||||
{stat: "level8SpellSlots", name: "level 8", group: "Spell Slots"},
|
{stat: "level8SpellSlots", name: "level 8", group: "Spell Slots"},
|
||||||
{stat: "level9SpellSlots", name: "level 9", 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: "d6HitDice", name: "d6 Hit Dice", group: "Hit Dice"},
|
||||||
{stat: "d10HitDice", name: "d10", group: "Hit Dice"},
|
{stat: "d8HitDice", name: "d8 Hit Dice", group: "Hit Dice"},
|
||||||
{stat: "d12HitDice", name: "d12", 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: "acidMultiplier", name: "Acid", group: "Weakness/Resistance"},
|
||||||
{stat: "bludgeoningMultiplier", name: "Bludgeoning", group: "Weakness/Resistance"},
|
{stat: "bludgeoningMultiplier", name: "Bludgeoning", group: "Weakness/Resistance"},
|
||||||
{stat: "coldMultiplier", name: "Cold", group: "Weakness/Resistance"},
|
{stat: "coldMultiplier", name: "Cold", group: "Weakness/Resistance"},
|
||||||
@@ -149,47 +156,71 @@ Template.effectEdit.helpers({
|
|||||||
effectValue: function(){
|
effectValue: function(){
|
||||||
return this.calculation || this.value;
|
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({
|
Template.effectEdit.events({
|
||||||
"click #deleteButton": function(event, instance){
|
"click #deleteButton": function(event, instance){
|
||||||
Effects.softRemoveNode(instance.data.id);
|
Effects.softRemoveNode(instance.data.id);
|
||||||
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
|
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
|
||||||
popDialogStack();
|
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){
|
"iron-select .statMenu": function(event){
|
||||||
var detail = event.originalEvent.detail;
|
var detail = event.originalEvent.detail;
|
||||||
var statName = detail.item.getAttribute("name");
|
var statName = detail.item.getAttribute("name");
|
||||||
if (statName == this.stat) return;
|
if (statName == this.stat) return;
|
||||||
var setter = {stat: statName};
|
setStat(statName, this._id);
|
||||||
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});
|
|
||||||
},
|
},
|
||||||
"iron-select .operationMenu": function(event){
|
"iron-select .operationMenu": function(event){
|
||||||
var detail = event.originalEvent.detail;
|
var detail = event.originalEvent.detail;
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ var stats = {
|
|||||||
"intelligence":{"name":"Intelligence"},
|
"intelligence":{"name":"Intelligence"},
|
||||||
"wisdom":{"name":"Wisdom"},
|
"wisdom":{"name":"Wisdom"},
|
||||||
"charisma":{"name":"Charisma"},
|
"charisma":{"name":"Charisma"},
|
||||||
|
|
||||||
"strengthSave":{"name":"Strength Save"},
|
"strengthSave":{"name":"Strength Save"},
|
||||||
"dexteritySave":{"name":"Dexterity Save"},
|
"dexteritySave":{"name":"Dexterity Save"},
|
||||||
"constitutionSave":{"name":"Constitution Save"},
|
"constitutionSave":{"name":"Constitution Save"},
|
||||||
"intelligenceSave":{"name":"Intelligence Save"},
|
"intelligenceSave":{"name":"Intelligence Save"},
|
||||||
"wisdomSave":{"name":"Wisdom Save"},
|
"wisdomSave":{"name":"Wisdom Save"},
|
||||||
"charismaSave":{"name":"Charisma Save"},
|
"charismaSave":{"name":"Charisma Save"},
|
||||||
|
|
||||||
"acrobatics":{"name":"Acrobatics"},
|
"acrobatics":{"name":"Acrobatics"},
|
||||||
"animalHandling":{"name":"Animal Handling"},
|
"animalHandling":{"name":"Animal Handling"},
|
||||||
"arcana":{"name":"Arcana"},
|
"arcana":{"name":"Arcana"},
|
||||||
@@ -30,7 +32,9 @@ var stats = {
|
|||||||
"stealth":{"name":"Stealth"},
|
"stealth":{"name":"Stealth"},
|
||||||
"survival":{"name":"Survival"},
|
"survival":{"name":"Survival"},
|
||||||
"initiative":{"name":"Initiative"},
|
"initiative":{"name":"Initiative"},
|
||||||
|
|
||||||
"hitPoints":{"name":"Hit Points"},
|
"hitPoints":{"name":"Hit Points"},
|
||||||
|
"tempHP":{"name":"Temporary Hit Points"},
|
||||||
"armor":{"name":"Armor"},
|
"armor":{"name":"Armor"},
|
||||||
"dexterityArmor":{"name":"Dexterity Armor Bonus"},
|
"dexterityArmor":{"name":"Dexterity Armor Bonus"},
|
||||||
"speed":{"name":"Speed"},
|
"speed":{"name":"Speed"},
|
||||||
@@ -42,6 +46,7 @@ var stats = {
|
|||||||
"expertiseDice":{"name":"Expertise Dice"},
|
"expertiseDice":{"name":"Expertise Dice"},
|
||||||
"superiorityDice":{"name":"Superiority Dice"},
|
"superiorityDice":{"name":"Superiority Dice"},
|
||||||
"carryMultiplier": {"name": "Carry Capacity Multiplier"},
|
"carryMultiplier": {"name": "Carry Capacity Multiplier"},
|
||||||
|
|
||||||
"level1SpellSlots":{"name":"level 1 Spell Slots"},
|
"level1SpellSlots":{"name":"level 1 Spell Slots"},
|
||||||
"level2SpellSlots":{"name":"level 2 Spell Slots"},
|
"level2SpellSlots":{"name":"level 2 Spell Slots"},
|
||||||
"level3SpellSlots":{"name":"level 3 Spell Slots"},
|
"level3SpellSlots":{"name":"level 3 Spell Slots"},
|
||||||
@@ -51,10 +56,12 @@ var stats = {
|
|||||||
"level7SpellSlots":{"name":"level 7 Spell Slots"},
|
"level7SpellSlots":{"name":"level 7 Spell Slots"},
|
||||||
"level8SpellSlots":{"name":"level 8 Spell Slots"},
|
"level8SpellSlots":{"name":"level 8 Spell Slots"},
|
||||||
"level9SpellSlots":{"name":"level 9 Spell Slots"},
|
"level9SpellSlots":{"name":"level 9 Spell Slots"},
|
||||||
|
|
||||||
"d6HitDice":{"name":"d6 Hit Dice"},
|
"d6HitDice":{"name":"d6 Hit Dice"},
|
||||||
"d8HitDice":{"name":"d8 Hit Dice"},
|
"d8HitDice":{"name":"d8 Hit Dice"},
|
||||||
"d10HitDice":{"name":"d10 Hit Dice"},
|
"d10HitDice":{"name":"d10 Hit Dice"},
|
||||||
"d12HitDice":{"name":"d12 Hit Dice"},
|
"d12HitDice":{"name":"d12 Hit Dice"},
|
||||||
|
|
||||||
"acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
|
"acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
|
||||||
"bludgeoningMultiplier":{
|
"bludgeoningMultiplier":{
|
||||||
"name":"Bludgeoning damage", "group": "Weakness/Resistance",
|
"name":"Bludgeoning damage", "group": "Weakness/Resistance",
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ Template.effectsEditList.events({
|
|||||||
template: "effectEdit",
|
template: "effectEdit",
|
||||||
data: {id: effectId},
|
data: {id: effectId},
|
||||||
element: event.currentTarget,
|
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){
|
"tap .edit-effect": function(event, template){
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
|
|
||||||
{{> effectsViewList charId=charId parentId=_id}}
|
{{> effectsViewList charId=charId parentId=_id}}
|
||||||
{{> proficiencyViewList charId=charId parentId=_id}}
|
{{> proficiencyViewList charId=charId parentId=_id}}
|
||||||
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="featureEdit">
|
<template name="featureEdit">
|
||||||
@@ -68,9 +71,14 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--description-->
|
<!--Description-->
|
||||||
<paper-textarea label="Description" id="featureDescriptionInput" value={{description}}></paper-textarea>
|
<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}}
|
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
|
||||||
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId 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>
|
</template>
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card.featureCard .bottom {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.containerMain.featureDescription {
|
.containerMain.featureDescription {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,30 +19,8 @@
|
|||||||
Attacks
|
Attacks
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom list">
|
<div class="bottom list">
|
||||||
{{#each attacks}}
|
{{#each attack in attacks}}
|
||||||
<div class="item-slot">
|
{{>attackListItem attack=attack charId=_id}}
|
||||||
<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}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</paper-material>
|
</paper-material>
|
||||||
@@ -55,19 +33,19 @@
|
|||||||
Proficiencies
|
Proficiencies
|
||||||
</div>
|
</div>
|
||||||
<div flex class="bottom list">
|
<div flex class="bottom list">
|
||||||
{{#if weaponProfs.count}}
|
{{#if weaponProfs.length}}
|
||||||
<div class="paper-font-subhead">Weapons</div>
|
<div class="paper-font-subhead">Weapons</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each weaponProfs}}
|
{{#each weaponProfs}}
|
||||||
{{> proficiencyListItem}}
|
{{> proficiencyListItem}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if armorProfs.count}}
|
{{#if armorProfs.length}}
|
||||||
<div class="paper-font-subhead">Armor</div>
|
<div class="paper-font-subhead">Armor</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each armorProfs}}
|
{{#each armorProfs}}
|
||||||
{{> proficiencyListItem}}
|
{{> proficiencyListItem}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{#if toolProfs.count}}
|
{{#if toolProfs.length}}
|
||||||
<div class="paper-font-subhead">Tools</div>
|
<div class="paper-font-subhead">Tools</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each toolProfs}}
|
{{#each toolProfs}}
|
||||||
@@ -100,9 +78,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#if description}}
|
{{#if hasCharacters (evaluateShortString charId description)}}
|
||||||
<div class="bottom flex">
|
<div class="bottom flex">
|
||||||
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
|
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
|
||||||
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if hasUses}}
|
{{#if hasUses}}
|
||||||
@@ -156,3 +135,29 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</template>
|
</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({
|
Template.features.helpers({
|
||||||
features: function(){
|
features: function(){
|
||||||
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
|
var features = Features.find({charId: this._id}, {sort: {color: 1, name: 1}});
|
||||||
@@ -27,13 +45,19 @@ Template.features.helpers({
|
|||||||
return !this.alwaysEnabled;
|
return !this.alwaysEnabled;
|
||||||
},
|
},
|
||||||
weaponProfs: function(){
|
weaponProfs: function(){
|
||||||
return Proficiencies.find({charId: this._id, type: "weapon"});
|
var profs = Proficiencies.find({charId: this._id, type: "weapon"});
|
||||||
|
return removeDuplicateProficiencies(profs);
|
||||||
},
|
},
|
||||||
armorProfs: function(){
|
armorProfs: function(){
|
||||||
return Proficiencies.find({charId: this._id, type: "armor"});
|
var profs = Proficiencies.find({charId: this._id, type: "armor"});
|
||||||
|
return removeDuplicateProficiencies(profs);
|
||||||
},
|
},
|
||||||
toolProfs: function(){
|
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/);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,13 +85,6 @@ Template.features.events({
|
|||||||
element: event.currentTarget.parentElement,
|
element: event.currentTarget.parentElement,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
"click .attack": function(event){
|
|
||||||
openParentDialog({
|
|
||||||
parent: this.parent,
|
|
||||||
charId: this.charId,
|
|
||||||
element: event.currentTarget,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
"click .useFeature": function(event){
|
"click .useFeature": function(event){
|
||||||
var featureId = this._id;
|
var featureId = this._id;
|
||||||
Features.update(featureId, {$inc: {used: 1}});
|
Features.update(featureId, {$inc: {used: 1}});
|
||||||
@@ -133,3 +150,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>
|
||||||
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
|
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
|
<paper-toggle-button id="carriedToggle" checked={{isCarried}}>
|
||||||
|
Carried
|
||||||
|
</paper-toggle-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="vertMargin">
|
<hr class="vertMargin">
|
||||||
|
<div class="description-input layout horizontal end">
|
||||||
<paper-textarea label="Description" id="containerDescriptionInput" value={{description}}>
|
<paper-textarea label="Description" id="containerDescriptionInput" value={{description}}>
|
||||||
</paper-textarea>
|
</paper-textarea>
|
||||||
|
{{> textareaBracketSuffix}}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="containerView">
|
<template name="containerView">
|
||||||
|
|||||||
@@ -54,4 +54,8 @@ Template.containerEdit.events({
|
|||||||
trimStrings: false,
|
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;">
|
<div class="bottom green" style="padding: 0;">
|
||||||
{{> carryCapacityBar}}
|
{{> carryCapacityBar}}
|
||||||
</div>
|
</div>
|
||||||
{{#if encumberedBuffs.count}}
|
{{#if encumberedConditions.count}}
|
||||||
<div class="bottom list">
|
<div class="bottom list">
|
||||||
{{#each encumberedBuffs}}
|
{{#each condition in encumberedConditions}}
|
||||||
<div class="item-slot">
|
<div class="item-slot">
|
||||||
<div class="item buff layout horizontal center">
|
<div class="item condition layout horizontal center">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<iron-icon icon="work"
|
<iron-icon icon="work"
|
||||||
style="margin-right: 16px">
|
style="margin-right: 16px">
|
||||||
</iron-icon>
|
</iron-icon>
|
||||||
{{name}}
|
{{condition.name}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -68,9 +68,8 @@ Template.inventory.helpers({
|
|||||||
return weight;
|
return weight;
|
||||||
},
|
},
|
||||||
encumberedBuffs: function(){
|
encumberedBuffs: function(){
|
||||||
return Buffs.find({
|
return Conditions.find({
|
||||||
charId: this._id,
|
charId: this._id,
|
||||||
type: "inate",
|
|
||||||
name: {$in: [
|
name: {$in: [
|
||||||
"Encumbered",
|
"Encumbered",
|
||||||
"Heavily encumbered",
|
"Heavily encumbered",
|
||||||
@@ -157,7 +156,7 @@ Template.inventory.events({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Make the library item into a regular item
|
// 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;
|
delete item.settings.category;
|
||||||
// Update the item to match library item
|
// Update the item to match library item
|
||||||
Items.update(itemId, {$set: item});
|
Items.update(itemId, {$set: item});
|
||||||
@@ -201,12 +200,10 @@ Template.inventory.events({
|
|||||||
element: event.currentTarget.parentElement,
|
element: event.currentTarget.parentElement,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
"click .buff": function(event, instance){
|
"click .condition": function(event, instance){
|
||||||
var buffId = this._id;
|
|
||||||
var charId = Template.parentData()._id;
|
|
||||||
pushDialogStack({
|
pushDialogStack({
|
||||||
template: "buffDialog",
|
template: "conditionViewDialogDialog",
|
||||||
data: {buffId: buffId, charId: charId},
|
data: {condition: this.condition},
|
||||||
element: event.currentTarget,
|
element: event.currentTarget,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -334,21 +331,23 @@ Template.layout.events({
|
|||||||
Session.set("inventory.dragItemId", null);
|
Session.set("inventory.dragItemId", null);
|
||||||
},
|
},
|
||||||
"drop .characterRepresentative": function(event, instance) {
|
"drop .characterRepresentative": function(event, instance) {
|
||||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||||
if (event.ctrlKey){
|
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||||
//split the stack to the container
|
if (event.ctrlKey){
|
||||||
pushDialogStack({
|
//split the stack to the container
|
||||||
template: "splitStackDialog",
|
pushDialogStack({
|
||||||
data: {
|
template: "splitStackDialog",
|
||||||
id: itemId,
|
data: {
|
||||||
parentCollection: "Characters",
|
id: itemId,
|
||||||
parentId: this._id,
|
parentCollection: "Characters",
|
||||||
},
|
parentId: this._id,
|
||||||
});
|
},
|
||||||
} else {
|
});
|
||||||
//move item to the character
|
} else {
|
||||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
//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}}
|
{{/if}}
|
||||||
{{> effectsViewList charId=charId parentId=_id}}
|
{{> effectsViewList charId=charId parentId=_id}}
|
||||||
{{> attacksViewList charId=charId parentId=_id}}
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="itemEdit">
|
<template name="itemEdit">
|
||||||
@@ -61,16 +62,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Description-->
|
<!--Description-->
|
||||||
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}>
|
<div class="description-input layout horizontal end">
|
||||||
<div suffix>
|
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}></paper-textarea>
|
||||||
<paper-tooltip position="left" animation-delay="0">This field accepts formulae in {curly brackets}</paper-tooltip>
|
{{> textareaBracketSuffix}}
|
||||||
<iron-icon icon="dicecloud:code-braces"></iron-icon>
|
</div>
|
||||||
</div>
|
|
||||||
</paper-textarea>
|
|
||||||
<!--Effects-->
|
<!--Effects-->
|
||||||
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
||||||
<!--Attacks-->
|
<!--Attacks-->
|
||||||
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
|
||||||
|
<!-- Buffs -->
|
||||||
|
{{> customBuffEditList parentId=_id parentCollection="Items" charId=charId}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="containerDropdown">
|
<template name="containerDropdown">
|
||||||
|
|||||||
@@ -61,15 +61,15 @@
|
|||||||
<template name="libraryItem">
|
<template name="libraryItem">
|
||||||
<tr class="item library-item {{#if selected}}selected{{/if}}">
|
<tr class="item library-item {{#if selected}}selected{{/if}}">
|
||||||
<td class="itemName">
|
<td class="itemName">
|
||||||
{{item.name}}
|
{{itemName}}
|
||||||
<paper-ripple></paper-ripple>
|
<paper-ripple></paper-ripple>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{item.weight}} lb.
|
{{itemWeight}} lb.
|
||||||
<paper-ripple></paper-ripple>
|
<paper-ripple></paper-ripple>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{{valueString item.value}}
|
{{valueString itemValue}}
|
||||||
<paper-ripple></paper-ripple>
|
<paper-ripple></paper-ripple>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -107,3 +107,24 @@ Template.itemLibraryDialog.events({
|
|||||||
template.searchTerm.set(value);
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|||||||
@@ -7,6 +7,24 @@ var colorMap = {
|
|||||||
backstory: "j",
|
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({
|
Template.persona.helpers({
|
||||||
characterDetails: function(){
|
characterDetails: function(){
|
||||||
var char = Characters.findOne(
|
var char = Characters.findOne(
|
||||||
@@ -33,7 +51,8 @@ Template.persona.helpers({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
languages: function(){
|
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({
|
Template.proficiencyListItem.events({
|
||||||
"click .proficiency": function(event, instance){
|
"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({
|
openParentDialog({
|
||||||
parent: this.parent,
|
parent: this.parent,
|
||||||
charId: this.charId,
|
charId: this.charId,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
<template name="spellDetails">
|
<template name="spellDetails">
|
||||||
<div class="paper-font-body2">
|
<div class="paper-font-body2">
|
||||||
Level {{level}} {{school}} {{#if ritual}}ritual{{/if}}, {{preparedString}}
|
{{schoolAndLevel}}{{#if ritual}} (ritual){{/if}}, {{preparedString}}
|
||||||
</div>
|
</div>
|
||||||
<div style="margin: 16px 0 16px 0;">
|
<div style="margin: 16px 0 16px 0;">
|
||||||
{{#if castingTime}}
|
{{#if castingTime}}
|
||||||
@@ -36,8 +36,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
|
<div>{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}</div>
|
||||||
{{> attacksViewList charId=charId parentId=_id}}
|
{{> attacksViewList charId=charId parentId=_id}}
|
||||||
|
{{> customBuffViewList charId=charId parentId=_id}}
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="spellEdit">
|
<template name="spellEdit">
|
||||||
@@ -112,7 +113,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--Description-->
|
<!--Description-->
|
||||||
<paper-textarea id="descriptionInput" label="Description" value="{{description}}">
|
<div class="description-input layout horizontal end">
|
||||||
</paper-textarea>
|
<paper-textarea id="descriptionInput" label="Description" style="width: calc(100% - 24px)" value={{description}}></paper-textarea>
|
||||||
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name}}
|
{{> textareaBracketSuffix}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{> customBuffEditList parentId=_id parentCollection="Spells" charId=charId}}
|
||||||
|
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name isSpell=true}}
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ Template.spellDialog.events({
|
|||||||
});
|
});
|
||||||
|
|
||||||
Template.spellDetails.helpers({
|
Template.spellDetails.helpers({
|
||||||
|
schoolAndLevel: function(){
|
||||||
|
if (this.level == 0) {
|
||||||
|
return this.school + " cantrip";
|
||||||
|
} else {
|
||||||
|
return "Level " + this.level + " " + this.school;
|
||||||
|
}
|
||||||
|
},
|
||||||
getComponents: function(){
|
getComponents: function(){
|
||||||
var components = "";
|
var components = "";
|
||||||
if (this.components.concentration) components += "C";
|
if (this.components.concentration) components += "C";
|
||||||
|
|||||||
@@ -40,8 +40,11 @@
|
|||||||
{{> formulaSuffix}}
|
{{> formulaSuffix}}
|
||||||
</paper-input>
|
</paper-input>
|
||||||
<!--Description-->
|
<!--Description-->
|
||||||
<paper-textarea id="spellListDescriptionInput" label="Description" value={{description}}>
|
<div class="description-input layout horizontal end">
|
||||||
</paper-textarea>
|
<paper-textarea id="spellListDescriptionInput" label="Description" value={{description}}>
|
||||||
|
</paper-textarea>
|
||||||
|
{{> textareaBracketSuffix}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/baseDialog}}
|
{{/baseDialog}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
|||||||
@@ -83,7 +83,9 @@
|
|||||||
{{#each spells ../_id ../../_id}}
|
{{#each spells ../_id ../../_id}}
|
||||||
{{#if showSpell ../../_id}}
|
{{#if showSpell ../../_id}}
|
||||||
<div class="item-slot">
|
<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"
|
<iron-icon icon="social:whatshot"
|
||||||
style="color: {{hexColor color}};
|
style="color: {{hexColor color}};
|
||||||
margin-right: 16px;"
|
margin-right: 16px;"
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ var spellLevels = [
|
|||||||
{name: "Level 9", level: 9},
|
{name: "Level 9", level: 9},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var materialNeedsGp = function(string) {
|
||||||
|
if (!string) return false;
|
||||||
|
gpRegExp = /\b[0-9]+ ?(cp|sp|gp)\b/i;
|
||||||
|
return gpRegExp.test(string);
|
||||||
|
}
|
||||||
|
|
||||||
const showUnprepared = (listId) => {
|
const showUnprepared = (listId) => {
|
||||||
return Session.get(`showUnprepared.${listId}`);
|
return Session.get(`showUnprepared.${listId}`);
|
||||||
}
|
}
|
||||||
@@ -70,6 +76,7 @@ Template.spells.helpers({
|
|||||||
}
|
}
|
||||||
if (this.components.material){
|
if (this.components.material){
|
||||||
components += components ? ", M" : "M";
|
components += components ? ", M" : "M";
|
||||||
|
if (materialNeedsGp(this.components.material)) {components += "gp";}
|
||||||
}
|
}
|
||||||
if (this.components.concentration){
|
if (this.components.concentration){
|
||||||
components += components ? ", C" : "C";
|
components += components ? ", C" : "C";
|
||||||
@@ -246,8 +253,8 @@ Template.spells.events({
|
|||||||
pushDialogStack({
|
pushDialogStack({
|
||||||
template: "spellLibraryDialog",
|
template: "spellLibraryDialog",
|
||||||
element: event.currentTarget,
|
element: event.currentTarget,
|
||||||
callback: (result) => {
|
callback: (resultArray) => {
|
||||||
if (!result) return;
|
if (!resultArray) return;
|
||||||
if (!listId){
|
if (!listId){
|
||||||
listId = SpellLists.insert({
|
listId = SpellLists.insert({
|
||||||
name: "New SpellList",
|
name: "New SpellList",
|
||||||
@@ -256,26 +263,56 @@ Template.spells.events({
|
|||||||
attackBonus: "intelligenceMod + proficiencyBonus",
|
attackBonus: "intelligenceMod + proficiencyBonus",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Make the library spell into a regular spell
|
|
||||||
let spell = _.omit(result, "library", "attacks", "effects");
|
//loop through all returned spells
|
||||||
spell._id = spellId;
|
_.each(resultArray, (rawSpell, index) =>{
|
||||||
spell.charId = charId;
|
// Make the library spell into a regular spell
|
||||||
spell.parent = {
|
let spell = _.omit(rawSpell, "library", "attacks", "effects");
|
||||||
id: listId,
|
if (index == 0) {
|
||||||
collection: "SpellLists",
|
spell._id = spellId; //only do this for the first spell added
|
||||||
};
|
}
|
||||||
spell.prepared = "prepared";
|
spell.charId = charId;
|
||||||
Spells.insert(spell);
|
spell.parent = {
|
||||||
// Copy over attacks and effects
|
id: listId,
|
||||||
_.each(result.attacks, (attack) => {
|
collection: "SpellLists",
|
||||||
attack.charId = charId;
|
};
|
||||||
attack.parent = {id: spellId, collection: "Spells"};
|
spell.prepared = "prepared";
|
||||||
Attacks.insert(attack);
|
Spells.insert(spell);
|
||||||
});
|
// Copy over attacks and effects
|
||||||
_.each(result.effects, (effect) => {
|
_.each(rawSpell.attacks, (attack) => {
|
||||||
effect.charId = charId;
|
if (!("attackBonus" in attack)) {attack.attackBonus = "attackBonus"} //if no attack bonus provided, use spell list's
|
||||||
effect.parent = {id: spellId, collection: "Spells"};
|
attack.charId = charId;
|
||||||
Effects.insert(effect);
|
attack.parent = {id: spellId, collection: "Spells"};
|
||||||
|
Attacks.insert(attack);
|
||||||
|
});
|
||||||
|
_.each(rawSpell.effects, (effect) => {
|
||||||
|
effect.charId = charId;
|
||||||
|
effect.parent = {id: spellId, collection: "Spells"};
|
||||||
|
Effects.insert(effect);
|
||||||
|
});
|
||||||
|
|
||||||
|
_.each(rawSpell.buffs, (buff) => {
|
||||||
|
buff.charId = charId;
|
||||||
|
buff.parent = {id: spellId, collection: "Spells"};
|
||||||
|
buffId = Buffs.insert(buff);
|
||||||
|
|
||||||
|
_.each(buff.attacks, (attack) => {
|
||||||
|
if (!(attackBonus in attack)) {attack.attackBonus = "attackBonus"} //if no attack bonus provided, use spell list's
|
||||||
|
attack.charId = charId;
|
||||||
|
attack.parent = {id: buffId, collection: "Buffs"};
|
||||||
|
Attacks.insert(attack);
|
||||||
|
});
|
||||||
|
_.each(buff.effects, (effect) => {
|
||||||
|
effect.charId = charId;
|
||||||
|
effect.parent = {id: buffId, collection: "Buffs"};
|
||||||
|
Effects.insert(effect);
|
||||||
|
});
|
||||||
|
_.each(buff.proficiencies, (prof) => {
|
||||||
|
prof.charId = charId;
|
||||||
|
prof.parent = {id: buffId, collection: "Buffs"};
|
||||||
|
Proficiencies.insert(prof);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
returnElement: () => $(`[data-id='${spellId}']`).get(0),
|
returnElement: () => $(`[data-id='${spellId}']`).get(0),
|
||||||
@@ -300,3 +337,49 @@ Template.spells.events({
|
|||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Template.layout.events({
|
||||||
|
"dragstart .spellItem": function(event, instance){
|
||||||
|
event.originalEvent.dataTransfer.setData("dicecloud-id/spells", this._id);
|
||||||
|
Session.set("spellLists.dragSpellId", this._id);
|
||||||
|
},
|
||||||
|
"dragend .spellItem": function(event, instance){
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
"dragover .spellList, dragenter .spellList": function(event, instance){
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drop .spellList": function(event, instance){
|
||||||
|
var spellId = event.originalEvent.dataTransfer.getData("dicecloud-id/spells");
|
||||||
|
if (event.ctrlKey){
|
||||||
|
//copy spell to new list
|
||||||
|
Meteor.call("copySpellToList", spellId, this._id);
|
||||||
|
} else {
|
||||||
|
//move spell to new list
|
||||||
|
Meteor.call("moveSpellToList", spellId, this._id);
|
||||||
|
}
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
},
|
||||||
|
|
||||||
|
"dragover .characterRepresentative, dragenter .characterRepresentative": function(event, instance){
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"drop .characterRepresentative": function(event, instance) {
|
||||||
|
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/spells")){ //to prevent conflicts with item drag/drop
|
||||||
|
var spellId = event.originalEvent.dataTransfer.getData("dicecloud-id/spells");
|
||||||
|
if (event.ctrlKey){
|
||||||
|
//copy spell to character
|
||||||
|
Meteor.call("copySpellToCharacter", spellId, this._id);
|
||||||
|
} else {
|
||||||
|
//move spell to character
|
||||||
|
Meteor.call("moveSpellToCharacter", spellId, this._id);
|
||||||
|
}
|
||||||
|
Session.set("spellLists.dragSpellId", null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
.spell-library-dialog .spell.selected {
|
|
||||||
background-color: #e4e4e4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spell-library-dialog .category-header {
|
.spell-library-dialog .category-header {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
@@ -13,11 +9,3 @@
|
|||||||
.spell-library-dialog .category-header iron-icon.open {
|
.spell-library-dialog .category-header iron-icon.open {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.spell-library-dialog table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spell-library-dialog .library-spell td {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,15 +13,18 @@
|
|||||||
<div class="spells" style="padding:8px">
|
<div class="spells" style="padding:8px">
|
||||||
{{#if searchTerm}}
|
{{#if searchTerm}}
|
||||||
{{#if searchSpells.count}}
|
{{#if searchSpells.count}}
|
||||||
<table style="width: 100%">
|
<div>
|
||||||
<tbody>
|
{{#each spell in searchSpells}}
|
||||||
{{#each spell in searchSpells}}
|
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
||||||
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
{{/each}}
|
||||||
{{/each}}
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{else}}{{#if searchReady}}
|
{{else}}{{#if searchReady}}
|
||||||
No spells match "{{searchTerm}}"
|
<p>
|
||||||
|
No spells match "{{searchTerm}}"
|
||||||
|
</p>
|
||||||
|
<p class="paper-font-caption">
|
||||||
|
DiceCloud only includes content provided by Wizards of the Coast in the official system reference document. If the spell you are looking for is not available in the system reference document, you will need to add it manually.
|
||||||
|
</p>
|
||||||
{{/if}}{{/if}}
|
{{/if}}{{/if}}
|
||||||
{{#unless searchReady}}
|
{{#unless searchReady}}
|
||||||
<div class="layout vertical center" style="width: 100%; padding: 16px;">
|
<div class="layout vertical center" style="width: 100%; padding: 16px;">
|
||||||
@@ -36,13 +39,11 @@
|
|||||||
{{name}}
|
{{name}}
|
||||||
</div>
|
</div>
|
||||||
<iron-collapse opened={{isOpen key}}>
|
<iron-collapse opened={{isOpen key}}>
|
||||||
<table style="width: 100%">
|
<div>
|
||||||
<tbody>
|
{{#each spell in (spellsInCategory key)}}
|
||||||
{{#each spell in (spellsInCategory key)}}
|
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
||||||
{{>librarySpell spell=spell selected=(isSelected spell)}}
|
{{/each}}
|
||||||
{{/each}}
|
</div>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{#unless ready key}}
|
{{#unless ready key}}
|
||||||
<paper-spinner active></paper-spinner>
|
<paper-spinner active></paper-spinner>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
@@ -59,10 +60,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="librarySpell">
|
<template name="librarySpell">
|
||||||
<tr class="spell library-spell {{#if selected}}selected{{/if}}">
|
<div style="margin: ">
|
||||||
<td class="spellName">
|
<paper-checkbox class="spell library-spell" checked={{selected}} style="padding: 2px 0 2px 16px; width: 100%;">
|
||||||
{{spell.name}}
|
{{spell.name}}
|
||||||
<paper-ripple></paper-ripple>
|
<paper-ripple></paper-ripple>
|
||||||
</td>
|
</paper-checkbox>
|
||||||
</tr>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const categories = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
Template.spellLibraryDialog.onCreated(function(){
|
Template.spellLibraryDialog.onCreated(function(){
|
||||||
this.selectedSpell = new ReactiveVar();
|
this.selectedSpells = new ReactiveVar([]); //this holds an array of the selected spells by ID
|
||||||
this.searchTerm = new ReactiveVar();
|
this.searchTerm = new ReactiveVar();
|
||||||
this.categoriesOpen = new ReactiveVar([]);
|
this.categoriesOpen = new ReactiveVar([]);
|
||||||
this.readyDict = new ReactiveDict();
|
this.readyDict = new ReactiveDict();
|
||||||
@@ -59,8 +59,12 @@ Template.spellLibraryDialog.helpers({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
isSelected(spell){
|
isSelected(spell){
|
||||||
const selected = Template.instance().selectedSpell.get();
|
const selected = Template.instance().selectedSpells.get();
|
||||||
return selected && selected._id === spell._id;
|
return _.contains(selected, spell._id);
|
||||||
|
},
|
||||||
|
selectedCount(){
|
||||||
|
const selected = Template.instance().selectedSpells.get();
|
||||||
|
return selected && selected.length;
|
||||||
},
|
},
|
||||||
isOpen(key){
|
isOpen(key){
|
||||||
const cats = Template.instance().categoriesOpen.get();
|
const cats = Template.instance().categoriesOpen.get();
|
||||||
@@ -89,10 +93,26 @@ Template.spellLibraryDialog.events({
|
|||||||
popDialogStack();
|
popDialogStack();
|
||||||
},
|
},
|
||||||
"click .okButton": function(event, template){
|
"click .okButton": function(event, template){
|
||||||
popDialogStack(template.selectedSpell.get());
|
const selectedIds = template.selectedSpells.get();
|
||||||
|
var returnSpells = [];
|
||||||
|
_.each(selectedIds, (id) => {
|
||||||
|
let spell = LibrarySpells.findOne(id);
|
||||||
|
if (spell) {
|
||||||
|
returnSpells.push(spell)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
popDialogStack(returnSpells);
|
||||||
},
|
},
|
||||||
"click .library-spell": function(event, template){
|
"click .library-spell": function(event, template){
|
||||||
template.selectedSpell.set(this.spell);
|
let selected = template.selectedSpells.get();
|
||||||
|
const spellId = this.spell._id;
|
||||||
|
// Toggle whether this spellId is in the array or not
|
||||||
|
if (_.contains(selected, spellId)){
|
||||||
|
selected = _.without(selected, spellId);
|
||||||
|
} else {
|
||||||
|
selected.push(spellId);
|
||||||
|
}
|
||||||
|
template.selectedSpells.set(selected);
|
||||||
},
|
},
|
||||||
"click #backButton": function(event, template){
|
"click #backButton": function(event, template){
|
||||||
popDialogStack();
|
popDialogStack();
|
||||||
|
|||||||
@@ -2,12 +2,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<paper-material class="ability-mini-card layout horizontal">
|
<paper-material class="ability-mini-card layout horizontal">
|
||||||
<div class="numbers">
|
<div class="numbers">
|
||||||
|
{{#if swap}}
|
||||||
|
<div class="paper-font-display1 stat">
|
||||||
|
{{abilityMod}}
|
||||||
|
</div>
|
||||||
|
<div class="paper-font-subhead modifier">
|
||||||
|
{{characterCalculate "attributeValue" ../_id ability}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="paper-font-display1 stat">
|
<div class="paper-font-display1 stat">
|
||||||
{{characterCalculate "attributeValue" ../_id ability}}
|
{{characterCalculate "attributeValue" ../_id ability}}
|
||||||
</div>
|
</div>
|
||||||
<div class="paper-font-subhead modifier">
|
<div class="paper-font-subhead modifier">
|
||||||
{{abilityMod}}
|
{{abilityMod}}
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="paper-font-subhead title flex layout horizontal center">
|
<div class="paper-font-subhead title flex layout horizontal center">
|
||||||
{{title}}
|
{{title}}
|
||||||
|
|||||||
@@ -5,5 +5,10 @@ Template.abilityMiniCard.helpers({
|
|||||||
Template.parentData()._id, this.ability
|
Template.parentData()._id, this.ability
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
swap: function() {
|
||||||
|
var character = Characters.findOne({"_id": Template.parentData()._id})
|
||||||
|
if (character) {return character.settings.swapStatAndModifier;}
|
||||||
|
else {return false;}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- data just needs charId -->
|
<!-- data just needs charId -->
|
||||||
<template name="addTHPDialog">
|
<template name="addEHPDialog">
|
||||||
<div class="fit layout vertical">
|
<div class="fit layout vertical">
|
||||||
<app-header-layout has-scrolling-region class="new-character-dialog flex">
|
<app-header-layout has-scrolling-region class="new-character-dialog flex">
|
||||||
<app-header fixed effects="waterfall">
|
<app-header fixed effects="waterfall">
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
Template.addTHPDialog.onRendered(function(){
|
Template.addEHPDialog.onRendered(function(){
|
||||||
this.find("#quantityInput").focus();
|
this.find("#quantityInput").focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.addTHPDialog.events({
|
Template.addEHPDialog.events({
|
||||||
"tap .addButton": function(event, instance){
|
"tap .addButton": function(event, instance){
|
||||||
popDialogStack();
|
popDialogStack();
|
||||||
var max = +instance.find("#quantityInput").value;
|
var max = +instance.find("#quantityInput").value;
|
||||||
@@ -3,10 +3,15 @@
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.healthCard .bottom-border {
|
||||||
|
border-bottom: rgba(0,0,0,0.24) solid 1px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.healthCard #stableButton {
|
.healthCard #stableButton {
|
||||||
color: #b71c1c;
|
color: #b71c1c;
|
||||||
transition: color 0.4s ease;
|
transition: color 0.4s ease;
|
||||||
width: 100%
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.healthCard #stableButton:before {
|
.healthCard #stableButton:before {
|
||||||
|
|||||||
@@ -5,33 +5,52 @@
|
|||||||
Hit Points
|
Hit Points
|
||||||
</div>
|
</div>
|
||||||
<paper-icon-button class="white54"
|
<paper-icon-button class="white54"
|
||||||
id="addTempHP"
|
id="addExtraHP"
|
||||||
icon="add"
|
icon="add"
|
||||||
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
|
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
|
||||||
</paper-icon-button>
|
</paper-icon-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="right flex layout vertical center-justified" style="min-width: 180px;">
|
<div class="right flex layout vertical center-justified" style="min-width: 180px;">
|
||||||
|
<!-- main HP slider -->
|
||||||
<div class="layout horizontal">
|
<div class="layout horizontal">
|
||||||
<paper-diff-slider id="hitPointSlider"
|
<paper-diff-slider id="hitPointSlider"
|
||||||
editable pin
|
editable pin
|
||||||
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
|
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
|
||||||
</paper-diff-slider>
|
</paper-diff-slider>
|
||||||
</div>
|
</div>
|
||||||
{{#each tempHitPoints}}
|
{{#if characterCalculate "attributeBase" _id "tempHP"}}
|
||||||
<div>
|
<!-- main THP slider -->
|
||||||
{{name}}
|
<div class="layout horizontal center {{#if extraHitPoints.count}}bottom-border{{/if}}">
|
||||||
|
<div class="self-center">
|
||||||
|
Temporary Hit Points
|
||||||
|
</div>
|
||||||
|
<paper-diff-slider id="temporaryHitPointSlider"
|
||||||
|
class="flex"
|
||||||
|
editable pin
|
||||||
|
disabled={{#unless canEditCharacter _id}}true{{/unless}}
|
||||||
|
max={{characterCalculate "attributeBase" _id "tempHP"}}
|
||||||
|
value={{characterCalculate "attributeValue" _id "tempHP"}}
|
||||||
|
>
|
||||||
|
</paper-diff-slider>
|
||||||
</div>
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#each extraHitPoints}}
|
||||||
<div class="layout horizontal center">
|
<div class="layout horizontal center">
|
||||||
|
<div class="self-center">
|
||||||
|
{{name}}
|
||||||
|
</div>
|
||||||
<div style="height: 40px; width: 40px;">
|
<div style="height: 40px; width: 40px;">
|
||||||
{{#unless left}}
|
{{#unless left}}
|
||||||
<paper-icon-button class="deleteTHP" icon="delete"></paper-icon-button>
|
<paper-icon-button class="deleteEHP" icon="delete"></paper-icon-button>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
</div>
|
</div>
|
||||||
<paper-diff-slider class="tempHitPointSlider flex"
|
<paper-diff-slider class="extraHitPointSlider flex"
|
||||||
max={{maximum}}
|
max={{maximum}}
|
||||||
value={{left}}
|
value={{left}}
|
||||||
editable pin
|
editable pin
|
||||||
></paper-diff-slider>
|
>
|
||||||
|
</paper-diff-slider>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
<div class="paper-font-caption">
|
<div class="paper-font-caption">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Template.healthCard.binding({
|
|||||||
"#hitPointSlider": {
|
"#hitPointSlider": {
|
||||||
max: () => Characters.calculate.attributeBase(currentId() , "hitPoints"),
|
max: () => Characters.calculate.attributeBase(currentId() , "hitPoints"),
|
||||||
value: () => Characters.calculate.attributeValue(currentId() , "hitPoints"),
|
value: () => Characters.calculate.attributeValue(currentId() , "hitPoints"),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset the old value between characters so that we don't get red health lost
|
// Reset the old value between characters so that we don't get red health lost
|
||||||
@@ -16,13 +16,14 @@ Template.healthCard.onRendered(function(){
|
|||||||
const id = Template.currentData()._id;
|
const id = Template.currentData()._id;
|
||||||
if (oldId !== id){
|
if (oldId !== id){
|
||||||
this.find("#hitPointSlider").resetOldValue();
|
this.find("#hitPointSlider").resetOldValue();
|
||||||
|
this.find("#temporaryHitPointSlider").resetOldValue();
|
||||||
oldId = id;
|
oldId = id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.healthCard.helpers({
|
Template.healthCard.helpers({
|
||||||
tempHitPoints: function(){
|
extraHitPoints: function(){
|
||||||
return TemporaryHitPoints.find({charId: this._id});
|
return TemporaryHitPoints.find({charId: this._id});
|
||||||
},
|
},
|
||||||
showDeathSave: function(){
|
showDeathSave: function(){
|
||||||
@@ -93,17 +94,23 @@ Template.healthCard.events({
|
|||||||
}}
|
}}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
"change .tempHitPointSlider": function(event){
|
"change #temporaryHitPointSlider": function(event){ //this is the actual THP stat
|
||||||
|
var value = event.currentTarget.value;
|
||||||
|
var base = Characters.calculate.attributeBase(this._id, "tempHP");
|
||||||
|
var adjustment = value - base;
|
||||||
|
Characters.update(this._id, {$set: {"tempHP.adjustment": adjustment}});
|
||||||
|
},
|
||||||
|
"change .extraHitPointSlider": function(event){ //this is the extra bars
|
||||||
var value = event.currentTarget.value;
|
var value = event.currentTarget.value;
|
||||||
var used = this.maximum - value;
|
var used = this.maximum - value;
|
||||||
TemporaryHitPoints.update(this._id, {$set: {"used": used}});
|
TemporaryHitPoints.update(this._id, {$set: {"used": used}});
|
||||||
},
|
},
|
||||||
"click .deleteTHP": function(event){
|
"click .deleteEHP": function(event){
|
||||||
TemporaryHitPoints.remove(this._id);
|
TemporaryHitPoints.remove(this._id);
|
||||||
},
|
},
|
||||||
"click #addTempHP": function(event){
|
"click #addExtraHP": function(event){
|
||||||
pushDialogStack({
|
pushDialogStack({
|
||||||
template: "addTHPDialog",
|
template: "addEHPDialog",
|
||||||
data: {charId: this._id},
|
data: {charId: this._id},
|
||||||
element: event.currentTarget.parentElement,
|
element: event.currentTarget.parentElement,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,6 +61,18 @@
|
|||||||
<td>Total</td>
|
<td>Total</td>
|
||||||
<td>{{characterCalculate "skillMod" charId skillName}}</td>
|
<td>{{characterCalculate "skillMod" charId skillName}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{{#each passiveEffects}}
|
||||||
|
<tr>
|
||||||
|
<td>{{sourceName}}</td>
|
||||||
|
<td>Passive Bonus: {{statValue}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/each}}
|
||||||
|
{{#if showPassiveTotal}}
|
||||||
|
<tr class="paper-font-body2">
|
||||||
|
<td>Passive Score</td>
|
||||||
|
<td>{{characterCalculate "passiveSkill" charId skillName}}</td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ Template.skillDialogView.helpers({
|
|||||||
var char = Characters.findOne(this.charId);
|
var char = Characters.findOne(this.charId);
|
||||||
if (!char) return;
|
if (!char) return;
|
||||||
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
|
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
|
||||||
var proficiencyBonus =
|
var proficiencyBonus =
|
||||||
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
|
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
|
||||||
return prof * proficiencyBonus;
|
return prof * proficiencyBonus;
|
||||||
},
|
},
|
||||||
@@ -189,6 +189,23 @@ Template.skillDialogView.helpers({
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
passiveEffects: function(){
|
||||||
|
return Effects.find({
|
||||||
|
charId: this.charId,
|
||||||
|
stat: this.skillName,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showPassiveTotal: function(){
|
||||||
|
if (this.skillName === "perception") return true;
|
||||||
|
return Effects.find({
|
||||||
|
charId: this.charId,
|
||||||
|
stat: this.skillName,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
}).count();
|
||||||
|
},
|
||||||
ability: function(){
|
ability: function(){
|
||||||
var opts = {fields: {}};
|
var opts = {fields: {}};
|
||||||
opts.fields[this.skillName] = 1;
|
opts.fields[this.skillName] = 1;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
{{#if conditionalCount}}
|
{{#if conditionalCount}}
|
||||||
*
|
*
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if showPassive}}
|
{{#if isPassiveShown}}
|
||||||
({{characterCalculate "passiveSkill" ../_id skill}})
|
({{characterCalculate "passiveSkill" ../_id skill}})
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,4 +38,16 @@ Template.skillRow.helpers({
|
|||||||
operation: "conditional",
|
operation: "conditional",
|
||||||
}).count();
|
}).count();
|
||||||
},
|
},
|
||||||
|
isPassiveShown: function(){
|
||||||
|
if (this.showPassive === "forced") return true;
|
||||||
|
if (this.showPassive === "ifNeeded"){
|
||||||
|
var charId = Template.parentData()._id;
|
||||||
|
return Effects.find({
|
||||||
|
charId,
|
||||||
|
stat: this.skill,
|
||||||
|
operation: "passiveAdd",
|
||||||
|
enabled: true,
|
||||||
|
}).count();
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,6 +42,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</paper-material>
|
</paper-material>
|
||||||
</div>
|
</div>
|
||||||
|
<!--Condtions-->
|
||||||
|
<div>
|
||||||
|
<paper-material class="card">
|
||||||
|
<div class="top white subhead layout horizontal center">
|
||||||
|
<div class="flex">Conditions</div>
|
||||||
|
{{#if canEditCharacter _id}}
|
||||||
|
<paper-icon-button class="black54" id="addCondition" icon="add"></paper-icon-button>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div flex class="bottom list">
|
||||||
|
<div class="conditionsList">
|
||||||
|
{{#each condition in conditions}}
|
||||||
|
{{>conditionView condition=condition}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{#if buffs.count}}
|
||||||
|
<div class="layout horizontal">
|
||||||
|
<div class="paper-font-subhead flex">Buffs</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="buffsList">
|
||||||
|
{{#each buff in buffs}}
|
||||||
|
{{>buffListItem buff=buff}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</paper-material>
|
||||||
|
</div>
|
||||||
<!--Skills-->
|
<!--Skills-->
|
||||||
<div>
|
<div>
|
||||||
<paper-material class="card">
|
<paper-material class="card">
|
||||||
@@ -49,24 +77,24 @@
|
|||||||
Skills
|
Skills
|
||||||
</div>
|
</div>
|
||||||
<div flex class="bottom list">
|
<div flex class="bottom list">
|
||||||
{{> skillRow name="Acrobatics" skill="acrobatics"}}
|
{{> skillRow name="Acrobatics" skill="acrobatics" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Animal Handling" skill="animalHandling"}}
|
{{> skillRow name="Animal Handling" skill="animalHandling" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Arcana" skill="arcana"}}
|
{{> skillRow name="Arcana" skill="arcana" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Athletics" skill="athletics"}}
|
{{> skillRow name="Athletics" skill="athletics" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Deception" skill="deception"}}
|
{{> skillRow name="Deception" skill="deception" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="History" skill="history"}}
|
{{> skillRow name="History" skill="history" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Insight" skill="insight"}}
|
{{> skillRow name="Insight" skill="insight" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Intimidation" skill="intimidation"}}
|
{{> skillRow name="Intimidation" skill="intimidation" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Investigation" skill="investigation"}}
|
{{> skillRow name="Investigation" skill="investigation" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Medicine" skill="medicine"}}
|
{{> skillRow name="Medicine" skill="medicine" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Nature" skill="nature"}}
|
{{> skillRow name="Nature" skill="nature" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Perception" skill="perception" showPassive="true"}}
|
{{> skillRow name="Perception" skill="perception" showPassive="forced"}}
|
||||||
{{> skillRow name="Performance" skill="performance"}}
|
{{> skillRow name="Performance" skill="performance" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Persuasion" skill="persuasion"}}
|
{{> skillRow name="Persuasion" skill="persuasion" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Religion" skill="religion"}}
|
{{> skillRow name="Religion" skill="religion" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Sleight of Hand" skill="sleightOfHand"}}
|
{{> skillRow name="Sleight of Hand" skill="sleightOfHand" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Stealth" skill="stealth"}}
|
{{> skillRow name="Stealth" skill="stealth" showPassive="ifNeeded"}}
|
||||||
{{> skillRow name="Survival" skill="survival"}}
|
{{> skillRow name="Survival" skill="survival" showPassive="ifNeeded"}}
|
||||||
</div>
|
</div>
|
||||||
</paper-material>
|
</paper-material>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,15 @@
|
|||||||
|
Template.stats.helpers({
|
||||||
|
conditions: function() {
|
||||||
|
return Conditions.find({charId: this._id});
|
||||||
|
},
|
||||||
|
buffs: function() {
|
||||||
|
var selector = {
|
||||||
|
"charId": this._id,
|
||||||
|
};
|
||||||
|
return Buffs.find(selector);
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
Template.stats.events({
|
Template.stats.events({
|
||||||
"click .stat-card": function(event, instance){
|
"click .stat-card": function(event, instance){
|
||||||
var charId = instance.data._id;
|
var charId = instance.data._id;
|
||||||
@@ -65,4 +77,17 @@ Template.stats.events({
|
|||||||
element: event.currentTarget.parentElement.parentElement,
|
element: event.currentTarget.parentElement.parentElement,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
"click #addCondition": function(event, template){
|
||||||
|
pushDialogStack({
|
||||||
|
template: "conditionLibraryDialog",
|
||||||
|
element: event.currentTarget,
|
||||||
|
callback: (result) => {
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else Meteor.call("giveCondition", this._id, result)
|
||||||
|
},
|
||||||
|
//returnElement: () => $(`[data-id='${itemId}']`).get(0),
|
||||||
|
})
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,8 +8,16 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-card .image {
|
.partyHeader {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partyHeader iron-icon {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.partyHeader:hover iron-icon{
|
||||||
|
visibility: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-card .initials {
|
.character-card .initials {
|
||||||
|
|||||||
@@ -10,31 +10,27 @@
|
|||||||
{{#if currentUser}}
|
{{#if currentUser}}
|
||||||
{{#if characters.count}}
|
{{#if characters.count}}
|
||||||
<div class="character-list layout horizontal wrap">
|
<div class="character-list layout horizontal wrap">
|
||||||
{{# each characters}}
|
{{# each charactersWithNoParty}}
|
||||||
<a class="character-card flex layout vertical end-justified" href="/character/{{_id}}">
|
{{> characterCard}}
|
||||||
<iron-image class="fit {{colorClass}}"
|
|
||||||
sizing="cover" preload fade src={{picture}}>
|
|
||||||
</iron-image>
|
|
||||||
{{#unless picture}}
|
|
||||||
<div class="fit initials layout vertical center center-justified">
|
|
||||||
{{initials name}}
|
|
||||||
</div>
|
|
||||||
{{/unless}}
|
|
||||||
<paper-item>
|
|
||||||
<paper-item-body two-lines>
|
|
||||||
<div class="name white87">
|
|
||||||
{{name}}
|
|
||||||
</div>
|
|
||||||
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
|
|
||||||
{{alignment}} {{gender}} {{race}}
|
|
||||||
</div>
|
|
||||||
</paper-item-body>
|
|
||||||
</paper-item>
|
|
||||||
<paper-ripple></paper-ripple>
|
|
||||||
</a>
|
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{> gridPadding class="character-card flex layout vertical" num=12}}
|
{{> gridPadding class="character-card flex layout vertical" num=12}}
|
||||||
</div>
|
</div>
|
||||||
|
{{# each party in parties}}
|
||||||
|
<div class="party" data-id={{party._id}}>
|
||||||
|
{{#with party}}
|
||||||
|
<div class="partyHeader clickable paper-font-title padded">
|
||||||
|
{{name}}
|
||||||
|
<iron-icon icon="create"></iron-icon>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
<div class="character-list layout horizontal wrap">
|
||||||
|
{{# each charactersInParty party._id}}
|
||||||
|
{{> characterCard}}
|
||||||
|
{{/each}}
|
||||||
|
{{> gridPadding class="character-card flex layout vertical" num=12}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div layout vertical center center-justified class="padded">
|
<div layout vertical center center-justified class="padded">
|
||||||
<div>You don't seem to have any characters yet</div>
|
<div>You don't seem to have any characters yet</div>
|
||||||
@@ -47,9 +43,46 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div class="fab-buffer"></div>
|
<div class="fab-buffer"></div>
|
||||||
<paper-fab class="floatyButton addCharacter"
|
{{#fabMenu}}
|
||||||
icon="add"
|
<div>
|
||||||
title="Add"></paper-fab>
|
<paper-fab icon="social:group"
|
||||||
|
class="addParty"
|
||||||
|
mini>
|
||||||
|
</paper-fab>
|
||||||
|
<paper-tooltip position="left"> New Party </paper-tooltip>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<paper-fab icon="face"
|
||||||
|
class="addCharacter"
|
||||||
|
mini>
|
||||||
|
</paper-fab>
|
||||||
|
<paper-tooltip position="left"> New Character </paper-tooltip>
|
||||||
|
</div>
|
||||||
|
{{/fabMenu}}
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</app-header-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template name="characterCard">
|
||||||
|
<a class="character-card flex layout vertical end-justified" href="{{characterPath this}}">
|
||||||
|
<iron-image class="fit {{colorClass}}"
|
||||||
|
sizing="cover" preload fade src={{picture}}>
|
||||||
|
</iron-image>
|
||||||
|
{{#unless picture}}
|
||||||
|
<div class="fit initials layout vertical center center-justified">
|
||||||
|
{{initials name}}
|
||||||
|
</div>
|
||||||
|
{{/unless}}
|
||||||
|
<paper-item>
|
||||||
|
<paper-item-body two-lines>
|
||||||
|
<div class="name white87">
|
||||||
|
{{name}}
|
||||||
|
</div>
|
||||||
|
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
|
||||||
|
{{alignment}} {{gender}} {{race}}
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
</paper-item>
|
||||||
|
<paper-ripple></paper-ripple>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user