Compare commits

...

73 Commits
1.4.0 ... 1.5.2

Author SHA1 Message Date
Stefan Zermatten
7c2aed26a4 Fixed vMix export, included vMix parties 2017-09-13 14:01:07 +02:00
Stefan Zermatten
fab052050a Merge branch 'feature-vmix' 2017-09-13 10:01:46 +02:00
Stefan Zermatten
b7bdb141c8 Added basic vMix server side api 2017-09-13 10:01:32 +02:00
Stefan Zermatten
0b8fabde14 Merge branch 'enhancement-106' 2017-09-08 10:48:44 +02:00
Stefan Zermatten
3336e177d9 Merge branch 'enhancement-spell-library-style'
closes #106
2017-09-08 10:46:41 +02:00
Stefan Zermatten
2e9440e325 Merge branch 'enhancement-spell-library-style' 2017-09-08 09:47:53 +02:00
Stefan Zermatten
e4ac400cbd Replaced spell library spell selection highlighting with checkboxes
Makes it more intuitive that multiple items can be selected
2017-09-08 09:47:43 +02:00
Stefan Zermatten
f600999c5f Guarded against error when hiding feature description portion of card 2017-09-08 09:46:58 +02:00
Stefan Zermatten
0af905699a Uncommented buffs from spell library 2017-09-08 09:46:00 +02:00
Stefan Zermatten
6e900cfaae Merge pull request #123 from Dumbgenius/spells-improvements
Spells improvements
2017-09-08 08:30:56 +02:00
Stefan Zermatten
1f42d3c622 Merge pull request #128 from Dumbgenius/fix-127
[#127] Arrow library items should give 20 arrows rather than one "Arrows (20)"
2017-09-08 08:29:56 +02:00
Stefan Zermatten
ee453d968f Merge pull request #134 from Dumbgenius/fixbug-attackview-damage-type
attackView now correctly shows damage type
2017-09-07 16:31:26 +02:00
Stefan Zermatten
0850e59b30 Merge pull request #122 from Dumbgenius/feature-22-temp-hp, requires migration
Feature - TempHP as a character attribute,
2017-09-07 16:29:55 +02:00
Stefan Zermatten
f3e44cf033 Moved polymer styling to app-theme 2017-09-07 16:26:09 +02:00
Stefan Zermatten
907f9d15d4 Merge pull request #131 from Dumbgenius/feature-custom-buffs
Feature - custom buffs
2017-09-07 14:42:05 +02:00
Stefan Zermatten
4c3d5d40dd Merge branch 'master' into feature-custom-buffs 2017-09-07 14:41:33 +02:00
Stefan Zermatten
ef0deb20aa Swapped dropdown box for full character list 2017-09-07 14:36:45 +02:00
Stefan Zermatten
b43ee08d75 Fixed some editing bugs on new buffs, improved style 2017-09-07 14:33:59 +02:00
Stefan Zermatten
7e7f1ec997 Merge pull request #132 from Dumbgenius/enhancement-blank-descriptions
Removed blank bit of card on features with no short description
2017-09-07 12:03:32 +02:00
Stefan Zermatten
0f652f5c74 Merge pull request #126 from Dumbgenius/feature-unsharing-characters
Added ability to "unshare" a character.
2017-09-07 12:01:14 +02:00
Stefan Zermatten
3e02875eaf Merge pull request #130 from Dumbgenius/enhancement-85
Added "carried" checkbox to container edit screen (resolves #85)
2017-09-07 11:54:11 +02:00
Stefan Zermatten
b9a5230344 Merge pull request #129 from Dumbgenius/enhancement-84
Added attacks to features (resolves #84)
2017-09-07 11:49:03 +02:00
Stefan Zermatten
28780b96c3 Merge pull request #120 from Dumbgenius/feature-drag-drop-spells
Feature - drag and drop spells
2017-09-07 11:43:34 +02:00
Jacob
bec0b33805 attackView now correctly shows damage type 2017-09-06 18:45:43 +01:00
Jacob
ad9ccbe7ef Removed blank bit of card on features with no short description 2017-09-05 14:31:26 +01:00
Jacob
e2933c2df5 Fixed a couple of places where "Buff" should've been "Condition" 2017-09-05 13:56:15 +01:00
Jacob
87583fdac6 Added buff details to apply buff dialog 2017-09-05 13:48:26 +01:00
Jacob
68e1382aed Buffs/conditions now have a delete button on the list on Stats 2017-09-04 20:36:13 +01:00
Jacob
7b62c82e32 Buff and condition view dialogs now only have the delete button 2017-09-04 20:16:56 +01:00
Jacob
6dd92586a4 Conditions are now separate from buffs, like in #109 2017-09-04 20:16:38 +01:00
Jacob
b3d0db1f02 Merge branch 'master' into feature-custom-buffs 2017-09-04 18:22:04 +01:00
Jacob
5f35c71c9d Added "carried" checkbox to container edit screen
Resolves #85
2017-09-04 15:50:57 +01:00
Jacob
85baf4e5d1 Added attacks to features (resolves #84) 2017-09-04 15:35:52 +01:00
Jacob
15d797131e Fixes #127. Also fixes it for other ammo as well as iron spikes 2017-09-04 15:22:45 +01:00
Stefan Zermatten
06ac9f70c8 Merge pull request #119 from Dumbgenius/srd-fixes
SRD fixes,

Merging this pull request doesn't change the deployed version of DiceCloud, but indicates that the database has been updated using the affected JSON files
2017-09-04 10:50:42 +02:00
Jacob
471a3e274e Refactored code which applies buffs 2017-08-10 02:49:29 +01:00
Jacob
d4031dc4a7 Removed an unnecessary console log 2017-08-09 16:11:09 +01:00
Jacob
18e5ab3f21 Can now set target of buffs
...to "Self only", "Others only", or "Both".
2017-08-09 16:08:19 +01:00
Jacob
c9fe2f17a0 If buff's target is "self", dialog is no longer shown 2017-08-09 15:48:12 +01:00
Jacob
818cb3905f Can now access the name of public characters for buffs
the buffDialog can now access the name of a publicly-available character
so it can display their name as the caster
2017-08-09 15:30:33 +01:00
Jacob
64ef426035 Applying character no longer duplicated in buff apply list 2017-08-09 15:30:25 +01:00
Jacob
2b188f1a8d Merge branch 'master' into feature-custom-buffs
# Conflicts:
#	rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html
#	rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
2017-08-09 15:03:54 +01:00
Jacob
08735ea4f7 Added ability to add/edit buffs 2017-08-09 15:01:20 +01:00
Jacob
bce1b85600 Relocated the buff list to be inline in stats.html 2017-08-09 11:39:18 +01:00
Jacob
0d023e2ba3 Adding conditions now works again 2017-08-09 11:36:45 +01:00
Jacob
dad575de64 Buffs are now split into custom buffs and conditions 2017-08-09 11:20:49 +01:00
Jacob
cb648b4a28 Buffs now display who and what they were applied by. 2017-08-09 11:13:24 +01:00
Jacob
1279137362 Buffs now display attacks and proficiencies in buffDialog 2017-08-09 10:42:34 +01:00
Jacob
3c06529906 buffViewList is now split into "Conditions" and "Buffs" 2017-08-09 10:16:50 +01:00
Jacob
a3b0c6cafd Added a schema/collection pair for CustomBuffs 2017-08-09 10:06:11 +01:00
Jacob
a1d9f7f5bb Added ability to remove self from readers.
Closes #125.
2017-08-09 02:18:46 +01:00
Jacob
3b03e9c71c Spell library now allows you to select multiple spells 2017-07-30 23:27:29 +01:00
Jacob
0eea6f2386 Cantrips now display as "<school> cantrip" rather than "Level 0 <school>"
Also fixed spacing if "(ritual)" tag was not present
2017-07-30 21:20:27 +01:00
Jacob
8f8714d3e5 Altered spacing of headers within baseDialog 2017-07-30 21:14:02 +01:00
Jacob
7c38e8d70a Added newline before At Higher Levels on Healing Word. 2017-07-30 20:22:59 +01:00
Jacob
826859ca3f Added migration to add TempHP to all characters 2017-07-29 22:41:37 +01:00
Jacob
2ca13fbb56 Renamed tempHitPointSlider to extraHitPointSlider in the css 2017-07-29 21:45:02 +01:00
Jacob
6d801e0178 Renamed old "Temporary HP" to "Extra HP" (THP is now the actual attribute) 2017-07-29 21:42:48 +01:00
Jacob
9ddac7d5cd Temporary HP (the attribute) now appears on Stats page 2017-07-29 21:25:05 +01:00
Jacob
7a6f751e30 THP added as a character attribute 2017-07-29 20:09:48 +01:00
Jacob
2389768234 statOrder is now set programatically from an array
This makes it far easier to add new stats, as we don't have to update
every single number manually.
2017-07-29 19:20:26 +01:00
Jacob
c76fe99148 Can now move and copy spells between characters 2017-07-26 21:02:36 +01:00
Jacob
53afaa4f37 CTRL-dragging now copies spells 2017-07-26 19:54:18 +01:00
Jacob
3599b5fbc4 Added basic drag-and-drop functionality between spell lists
Closes #105
2017-07-26 19:23:41 +01:00
Jacob
275fb1ca65 Scrying's tables are now actual Markdown tables 2017-07-21 15:46:33 +01:00
Jacob
d5d937b04a Reincarnate's race table is now an actual Markdown table 2017-07-21 15:38:17 +01:00
Jacob
aa554adbce Teleport's familiarity table is now a table 2017-07-21 15:30:39 +01:00
Jacob
cb739eb207 Removed indents after double-newlines in spells SRD.
They don't actually render in Markdown, and if there was an indent of >=4
spaces it caused the entire next line to be rendered as code (for example
in the Animate Objects spell).
2017-07-21 14:58:29 +01:00
Jacob
66df2ea4aa Animate Objects' stats are now in a table 2017-07-21 14:43:37 +01:00
Jacob
bb95fe0d9a Merge branch 'master' into feature-conditions 2017-07-18 11:10:04 +01:00
Jacob
17a390a8aa Changed Model of Buffs for forwards compatibility
Added a parent attribute and made Buffs a child, so that buffs can be
added to items, spells and features.
2017-07-17 02:41:51 +01:00
Jacob
b3ef43eb70 Added ability to add/remove basic conditions
Conditions are those listed in ./lib/methods/conditions.js
2017-07-16 04:27:32 +01:00
Jacob
be92ef224c Preliminary additions 2017-07-15 19:54:23 +01:00
78 changed files with 2153 additions and 509 deletions

View File

@@ -21,32 +21,40 @@
"weight": 1
},
{
"name": "Arrows (20)",
"plural": "Arrows (20)",
"libraryName": "Arrows (20)",
"name": "Arrow",
"plural": "Arrows",
"description": "",
"value": 1,
"weight": 1
"value": 0.05,
"weight": 0.05,
"quantity": 20
},
{
"name": "Blowgun needles (5)",
"plural": "Blowgun needles (5)",
"libraryName": "Blowgun needles (5)",
"name": "Blowgun needle",
"plural": "Blowgun needles",
"description": "",
"value": 1,
"weight": 1
"value": 0.2,
"weight": 0.2,
"quantity": 5
},
{
"name": "Crossbow bolts (20)",
"plural": "Crossbow bolts (20)",
"libraryName": "Crossbow bolts (20)",
"name": "Crossbow bolt",
"plural": "Crossbow bolts",
"description": "",
"value": 1,
"weight": 1.5
"value": 0.05,
"weight": 0.075,
"quantity": 20
},
{
"name": "Sling bullets (20)",
"plural": "Sling bullets (20)",
"libraryName": "Sling bullets (20)",
"name": "Sling bullet",
"plural": "Sling bullets",
"description": "",
"value": 0.04,
"weight": 1.5
"value": 0.002,
"weight": 0.075,
"quantity": 20
},
{
"name": "Antitoxin (vial)",
@@ -651,11 +659,13 @@
"weight": 3
},
{
"name": "Spikes, iron (10)",
"plural": "Spikes, iron (10)",
"libraryName": "Spikes, iron (10)",
"name": "Spike, iron",
"plural": "Spikes, iron",
"description": "",
"value": 1,
"weight": 5
"value": 0.1,
"weight": 0.5,
"quantity": 10
},
{
"name": "Spyglass",

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ Schemas.Buff = new SimpleSchema({
type: {
type: String,
allowedValues: [
"inate",
"inate", //this should be "innate", but changing it could be problematic
"custom",
],
},
@@ -42,12 +42,26 @@ Schemas.Buff = new SimpleSchema({
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
appliedBy: { //the charId of whoever applied the buff
type: String,
regEx: SimpleSchema.RegEx.Id,
},
appliedByDetails: {//the name and collection of the thing that applied the buff
type: Object,
optional: true,
},
"appliedByDetails.name": {
type: String,
},
"appliedByDetails.collection": {
type: String,
},
});
Buffs.attachSchema(Schemas.Buff);
Buffs.attachBehaviour("softRemovable");
makeParent(Buffs, ["name", "enabled"]); //parents of effects
makeParent(Buffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);

View File

@@ -27,6 +27,7 @@ Schemas.Character = new SimpleSchema({
//stats
hitPoints: {type: Schemas.Attribute},
tempHP: {type: Schemas.Attribute},
experience: {type: Schemas.Attribute},
proficiencyBonus: {type: Schemas.Attribute},
speed: {type: Schemas.Attribute},
@@ -282,6 +283,7 @@ if (Meteor.isClient) {
//create a local memoize with a argument concatenating hash function
var memoize = function(f) {
if (Meteor.isServer) return f;
return Tracker.memoize(f, function() {
return _.reduce(arguments, function(memo, arg) {
return memo + arg;
@@ -533,6 +535,7 @@ if (Meteor.isServer){
Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id});
Classes .remove({charId: character._id});
CustomBuffs .remove({charId: character._id});
Effects .remove({charId: character._id});
Experiences .remove({charId: character._id});
Features .remove({charId: character._id});

View 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);

View 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);

View File

@@ -83,3 +83,170 @@ Spells.after.update(function (userId, spell, fieldNames) {
Spells.allow(CHARACTER_SUBSCHEMA_ALLOW);
Spells.deny(CHARACTER_SUBSCHEMA_DENY);
var checkMovePermission = function(spellId, parent, destinationOnly) {
var spell = Spells.findOne(spellId);
if (!spell)
throw new Meteor.Error("No such spell",
"An spell could not be found to move");
//handle permissions
var permission;
if (!destinationOnly) { //if we're not modifying the origin, only the destination
permission = Meteor.call("canWriteCharacter", spell.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move spells from this character");
}
}
if (parent.collection === "Characters"){
permission = Meteor.call("canWriteCharacter", parent.id);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move spells to this character");
}
} else {
var parentCollectionObject = global[parent.collection];
var parentObject = null;
if (parentCollectionObject)
parentObject = parentCollectionObject.findOne(
parent.id, {fields: {_id: 1, charId: 1}}
);
if (!parentObject) throw new Meteor.Error(
"Invalid parent",
"The destination parent " + parent.id +
" does not exist in the collection " + parent.collection
);
if (parentObject.charId){
permission = Meteor.call("canWriteCharacter", parentObject.charId);
if (!permission){
throw new Meteor.Error("Access denied",
"Not permitted to move spells to this character");
}
}
}
};
var moveSpell = function(spellId, targetCollection, targetId) {
var spell = Spells.findOne(spellId);
if (!spell) return;
targetCollection = targetCollection || spell.parent.collection;
targetId = targetId || spell.parent.id;
if (Meteor.isServer) {
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, false);
}
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
targetList = SpellLists.findOne({"charId": targetId});
targetListId = targetList && targetList._id;
if (!targetListId) {
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
name: "New SpellList",
charId: targetId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
Spells.update(
spellId,
{$set: {
charId: targetId,
"parent.collection": "SpellLists",
"parent.id": targetListId,
}}
);
}
else { //we are moving the spell within the same character
//update the spell provided the update will actually change something
if (
spell.parent.collection !== targetCollection ||
spell.parent.id !== targetId
){
Spells.update(
spellId,
{$set: {
"parent.collection": targetCollection,
"parent.id": targetId,
}}
);
}
}
};
var copySpell = function(spellId, targetCollection, targetId) {
var spell = Spells.findOne(spellId);
if (!spell) return;
targetCollection = targetCollection || spell.parent.collection;
targetId = targetId || spell.parent.id;
if (Meteor.isServer) {
checkMovePermission(spellId, {collection: targetCollection, id: targetId}, true); //we're only reading from the source character
}
if (targetCollection == "Characters") { //then we are copying the spell to a different character.
targetList = SpellLists.findOne({"charId": targetId});
targetListId = targetList && targetList._id;
if (!targetListId) {
targetListId = SpellLists.insert({ //create a spell list if we don't already have one
name: "New SpellList",
charId: targetId,
saveDC: "8 + intelligenceMod + proficiencyBonus",
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
newSpell = _.clone(spell);
delete newSpell._id;
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
Spells.update(
newSpellId,
{$set: {
charId: targetId,
"parent.collection": "SpellLists",
"parent.id": targetListId,
}}
);
}
else { //else we are copying the spell within the same character
newSpell = _.clone(spell);
delete newSpell._id;
newSpellId = Spells.insert(newSpell); //add a new copy of the spell
Spells.update(
newSpellId,
{$set: {
"parent.collection": targetCollection,
"parent.id": targetId,
}}
);
}
};
Meteor.methods({
moveSpellToList: function(spellId, spellListId) {
check(spellId, String);
check(spellListId, String);
moveSpell(spellId, "SpellLists", spellListId);
},
copySpellToList: function(spellId, spellListId) {
check(spellId, String);
check(spellListId, String);
copySpell(spellId, "SpellLists", spellListId);
},
moveSpellToCharacter: function(spellId, charId) {
check(spellId, String);
check(charId, String);
moveSpell(spellId, "Characters", charId);
},
copySpellToCharacter: function(spellId, charId) {
check(spellId, String);
check(charId, String);
copySpell(spellId, "Characters", charId);
},
});

View File

@@ -1,6 +1,7 @@
LibraryItems = new Mongo.Collection("libraryItems");
Schemas.LibraryItems = new SimpleSchema({
libraryName:{type: String, optional: true, trim: false},
name: {type: String, defaultValue: "New Item", trim: false},
plural: {type: String, optional: true, trim: false},
description:{type: String, optional: true, trim: false},

18
rpg-docs/Routes/API.js Normal file
View 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));
},
});
});

View File

@@ -18,6 +18,9 @@ openParentDialog = function({
} else if (parent.collection === "Spells") {
template = "spellDialog";
data = {spellId: parent.id};
} else if (parent.collection === "Buffs") {
template = "buffDialog";
data = {buffId: parent.id};
}
pushDialogStack({template, data, element, returnElement, callback});
};

View File

@@ -5,7 +5,7 @@
</div>
<div class="layout vertical">
<div>
{{evaluateDamage charId attack}}&nbsp;{{damageType}}
{{evaluateDamage charId attack}}&nbsp;{{attack.damageType}}
</div>
{{#if attack.details}}
<div class="paper-font-caption">

View File

@@ -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>

View File

@@ -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();
},
});

View File

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

View File

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

View File

@@ -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>

View File

@@ -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);
},
});

View File

@@ -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;
}

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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);
},
});

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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();
},
});

View File

@@ -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>

View File

@@ -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,
});
},
});

View File

@@ -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>

View File

@@ -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);
}
},
});

View File

@@ -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>

View File

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

View File

@@ -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>

View File

@@ -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();
},
});

View File

@@ -30,6 +30,16 @@
</paper-icon-item>
</paper-menu>
</paper-menu-button>
{{else}}
<paper-menu-button class="character-menu" horizontal-align="right">
<paper-icon-button icon="more-vert" class="dropdown-trigger"></paper-icon-button>
<paper-menu class="dropdown-content black87">
<paper-icon-item id="unshareCharacter">
<iron-icon icon="delete" item-icon></iron-icon>
Unshare
</paper-icon-item>
</paper-menu>
</paper-menu-button>
{{/if}}
</div>
<div bottom-item>

View File

@@ -210,4 +210,11 @@ Template.characterSheet.events({
element: event.currentTarget.parentElement.parentElement,
});
},
"click #unshareCharacter": function(event, instance){
pushDialogStack({
data: this,
template: "unshareCharacterConfirmation",
element: event.currentTarget.parentElement.parentElement,
});
},
});

View File

@@ -7,32 +7,36 @@ var stats = [
{stat: "intelligence", name: "Intelligence", group: "Ability Scores"},
{stat: "wisdom", name: "Wisdom", group: "Ability Scores"},
{stat: "charisma", name: "Charisma", group: "Ability Scores"},
{name: "Strength Save", stat: "strengthSave", group: "Saving Throws"},
{name: "Dexterity Save", stat: "dexteritySave", group: "Saving Throws"},
{name: "Constitution Save", stat: "constitutionSave", group: "Saving Throws"},
{name: "Intelligence Save", stat: "intelligenceSave", group: "Saving Throws"},
{name: "Wisdom Save", stat: "wisdomSave", group: "Saving Throws"},
{name: "Charisma Save", stat: "charismaSave", group: "Saving Throws"},
{name: "Acrobatics", stat: "acrobatics", group: "Skills"},
{name: "Animal Handling", stat: "animalHandling", group: "Skills"},
{name: "Arcana", stat: "arcana", group: "Skills"},
{name: "Athletics", stat: "athletics", group: "Skills"},
{name: "Deception", stat: "deception", group: "Skills"},
{name: "History", stat: "history", group: "Skills"},
{name: "Insight", stat: "insight", group: "Skills"},
{name: "Intimidation", stat: "intimidation", group: "Skills"},
{name: "Investigation", stat: "investigation", group: "Skills"},
{name: "Medicine", stat: "medicine", group: "Skills"},
{name: "Nature", stat: "nature", group: "Skills"},
{name: "Perception", stat: "perception", group: "Skills"},
{name: "Performance", stat: "performance", group: "Skills"},
{name: "Persuasion", stat: "persuasion", group: "Skills"},
{name: "Religion", stat: "religion", group: "Skills"},
{name: "Sleight of Hand", stat: "sleightOfHand", group: "Skills"},
{name: "Stealth", stat: "stealth", group: "Skills"},
{name: "Survival", stat: "survival", group: "Skills"},
{name: "Initiative", stat: "initiative", group: "Skills"},
{stat: "strengthSave", name: "Strength Save", group: "Saving Throws"},
{stat: "dexteritySave", name: "Dexterity Save", group: "Saving Throws"},
{stat: "constitutionSave", name: "Constitution Save", group: "Saving Throws"},
{stat: "intelligenceSave", name: "Intelligence Save", group: "Saving Throws"},
{stat: "wisdomSave", name: "Wisdom Save", group: "Saving Throws"},
{stat: "charismaSave", name: "Charisma Save", group: "Saving Throws"},
{stat: "acrobatics", name: "Acrobatics", group: "Skills"},
{stat: "animalHandling", name: "Animal Handling", group: "Skills"},
{stat: "arcana", name: "Arcana", group: "Skills"},
{stat: "athletics", name: "Athletics", group: "Skills"},
{stat: "deception", name: "Deception", group: "Skills"},
{stat: "history", name: "History", group: "Skills"},
{stat: "insight", name: "Insight", group: "Skills"},
{stat: "intimidation", name: "Intimidation", group: "Skills"},
{stat: "investigation", name: "Investigation", group: "Skills"},
{stat: "medicine", name: "Medicine", group: "Skills"},
{stat: "nature", name: "Nature", group: "Skills"},
{stat: "perception", name: "Perception", group: "Skills"},
{stat: "performance", name: "Performance", group: "Skills"},
{stat: "persuasion", name: "Persuasion", group: "Skills"},
{stat: "religion", name: "Religion", group: "Skills"},
{stat: "sleightOfHand", name: "Sleight of Hand", group: "Skills"},
{stat: "stealth", name: "Stealth", group: "Skills"},
{stat: "survival", name: "Survival", group: "Skills"},
{stat: "initiative", name: "Initiative", group: "Skills"},
{stat: "hitPoints", name: "Hit Points", group: "Stats"},
{stat: "tempHP", name: "Temporary Hit Points", group: "Stats"},
{stat: "armor", name: "Armor", group: "Stats"},
{stat: "dexterityArmor", name: "Dexterity Armor Bonus", group: "Stats"},
{stat: "speed", name: "Speed", group: "Stats"},
@@ -44,6 +48,7 @@ var stats = [
{stat: "expertiseDice", name: "Expertise Dice", group: "Stats"},
{stat: "superiorityDice", name: "Superiority Dice", group: "Stats"},
{stat: "carryMultiplier", name: "Carry Capacity Multiplier", group: "Stats"},
{stat: "level1SpellSlots", name: "level 1", group: "Spell Slots"},
{stat: "level2SpellSlots", name: "level 2", group: "Spell Slots"},
{stat: "level3SpellSlots", name: "level 3", group: "Spell Slots"},
@@ -53,10 +58,12 @@ var stats = [
{stat: "level7SpellSlots", name: "level 7", group: "Spell Slots"},
{stat: "level8SpellSlots", name: "level 8", group: "Spell Slots"},
{stat: "level9SpellSlots", name: "level 9", group: "Spell Slots"},
{stat: "d6HitDice", name: "d6 Hit Dice", group: "Hit Dice"},
{stat: "d8HitDice", name: "d8 Hit Dice", group: "Hit Dice"},
{stat: "d10HitDice", name: "d10 Hit Dice", group: "Hit Dice"},
{stat: "d12HitDice", name: "d12 Hit Dice", group: "Hit Dice"},
{stat: "acidMultiplier", name: "Acid", group: "Weakness/Resistance"},
{stat: "bludgeoningMultiplier", name: "Bludgeoning", group: "Weakness/Resistance"},
{stat: "coldMultiplier", name: "Cold", group: "Weakness/Resistance"},

View File

@@ -5,12 +5,14 @@ var stats = {
"intelligence":{"name":"Intelligence"},
"wisdom":{"name":"Wisdom"},
"charisma":{"name":"Charisma"},
"strengthSave":{"name":"Strength Save"},
"dexteritySave":{"name":"Dexterity Save"},
"constitutionSave":{"name":"Constitution Save"},
"intelligenceSave":{"name":"Intelligence Save"},
"wisdomSave":{"name":"Wisdom Save"},
"charismaSave":{"name":"Charisma Save"},
"acrobatics":{"name":"Acrobatics"},
"animalHandling":{"name":"Animal Handling"},
"arcana":{"name":"Arcana"},
@@ -30,7 +32,9 @@ var stats = {
"stealth":{"name":"Stealth"},
"survival":{"name":"Survival"},
"initiative":{"name":"Initiative"},
"hitPoints":{"name":"Hit Points"},
"tempHP":{"name":"Temporary Hit Points"},
"armor":{"name":"Armor"},
"dexterityArmor":{"name":"Dexterity Armor Bonus"},
"speed":{"name":"Speed"},
@@ -42,6 +46,7 @@ var stats = {
"expertiseDice":{"name":"Expertise Dice"},
"superiorityDice":{"name":"Superiority Dice"},
"carryMultiplier": {"name": "Carry Capacity Multiplier"},
"level1SpellSlots":{"name":"level 1 Spell Slots"},
"level2SpellSlots":{"name":"level 2 Spell Slots"},
"level3SpellSlots":{"name":"level 3 Spell Slots"},
@@ -51,10 +56,12 @@ var stats = {
"level7SpellSlots":{"name":"level 7 Spell Slots"},
"level8SpellSlots":{"name":"level 8 Spell Slots"},
"level9SpellSlots":{"name":"level 9 Spell Slots"},
"d6HitDice":{"name":"d6 Hit Dice"},
"d8HitDice":{"name":"d8 Hit Dice"},
"d10HitDice":{"name":"d10 Hit Dice"},
"d12HitDice":{"name":"d12 Hit Dice"},
"acidMultiplier":{"name":"Acid damage", "group": "Weakness/Resistance"},
"bludgeoningMultiplier":{
"name":"Bludgeoning damage", "group": "Weakness/Resistance",

View File

@@ -36,6 +36,9 @@
{{> effectsViewList charId=charId parentId=_id}}
{{> proficiencyViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}
{{> customBuffViewList charId=charId parentId=_id}}
</template>
<template name="featureEdit">
@@ -76,4 +79,6 @@
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
{{> attackEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled name=name}}
{{> customBuffEditList parentId=_id parentCollection="Features" charId=charId}}
</template>

View File

@@ -6,6 +6,10 @@
margin-bottom: 8px;
}
.card.featureCard .bottom {
padding-bottom: 8px;
}
.containerMain.featureDescription {
white-space: pre-line;
}

View File

@@ -78,9 +78,10 @@
</div>
{{/if}}
</div>
{{#if description}}
{{#if hasCharacters (evaluateShortString charId description)}}
<div class="bottom flex">
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
{{> customBuffViewList charId=charId parentId=_id}}
</div>
{{/if}}
{{#if hasUses}}
@@ -159,4 +160,4 @@
</div>
</div>
</div>
</template>
</template>

View File

@@ -56,6 +56,9 @@ Template.features.helpers({
var profs = Proficiencies.find({charId: this._id, type: "tool"});
return removeDuplicateProficiencies(profs);
},
hasCharacters: function(string){
return string && string.match(/\S/);
},
});
Template.features.events({

View File

@@ -17,6 +17,9 @@
</paper-input>
<paper-input id="valueInput" label="Value" type="number" value={{value}}>
</paper-input>
<paper-toggle-button id="carriedToggle" checked={{isCarried}}>
Carried
</paper-toggle-button>
</div>
<hr class="vertMargin">

View File

@@ -54,4 +54,8 @@ Template.containerEdit.events({
trimStrings: false,
});
},
"change #carriedToggle": function(event, instance){
var carried = !this.isCarried;
Containers.update(this._id, {$set: {isCarried: carried}});
}
});

View File

@@ -28,16 +28,16 @@
<div class="bottom green" style="padding: 0;">
{{> carryCapacityBar}}
</div>
{{#if encumberedBuffs.count}}
{{#if encumberedConditions.count}}
<div class="bottom list">
{{#each encumberedBuffs}}
{{#each condition in encumberedConditions}}
<div class="item-slot">
<div class="item buff layout horizontal center">
<div class="item condition layout horizontal center">
<div class="flex">
<iron-icon icon="work"
style="margin-right: 16px">
</iron-icon>
{{name}}
{{condition.name}}
</div>
</div>
</div>

View File

@@ -68,9 +68,8 @@ Template.inventory.helpers({
return weight;
},
encumberedBuffs: function(){
return Buffs.find({
return Conditions.find({
charId: this._id,
type: "inate",
name: {$in: [
"Encumbered",
"Heavily encumbered",
@@ -157,7 +156,7 @@ Template.inventory.events({
return;
}
// Make the library item into a regular item
let item = _.omit(result, "library", "attacks", "effects");
let item = _.omit(result, "libraryName", "library", "attacks", "effects");
delete item.settings.category;
// Update the item to match library item
Items.update(itemId, {$set: item});
@@ -201,12 +200,10 @@ Template.inventory.events({
element: event.currentTarget.parentElement,
});
},
"click .buff": function(event, instance){
var buffId = this._id;
var charId = Template.parentData()._id;
"click .condition": function(event, instance){
pushDialogStack({
template: "buffDialog",
data: {buffId: buffId, charId: charId},
template: "conditionViewDialogDialog",
data: {condition: this.condition},
element: event.currentTarget,
});
},
@@ -334,21 +331,23 @@ Template.layout.events({
Session.set("inventory.dragItemId", null);
},
"drop .characterRepresentative": function(event, instance) {
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
pushDialogStack({
template: "splitStackDialog",
data: {
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
//move item to the character
Meteor.call("moveItemToCharacter", itemId, this._id);
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
if (event.ctrlKey){
//split the stack to the container
pushDialogStack({
template: "splitStackDialog",
data: {
id: itemId,
parentCollection: "Characters",
parentId: this._id,
},
});
} else {
//move item to the character
Meteor.call("moveItemToCharacter", itemId, this._id);
}
Session.set("inventory.dragItemId", null);
}
Session.set("inventory.dragItemId", null);
},
});

View File

@@ -23,6 +23,7 @@
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}
{{> customBuffViewList charId=charId parentId=_id}}
</template>
<template name="itemEdit">
@@ -65,10 +66,13 @@
<paper-textarea id="itemDescriptionInput" label="Description" value={{description}}></paper-textarea>
{{> textareaBracketSuffix}}
</div>
<!--Effects-->
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
<!--Attacks-->
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
<!-- Buffs -->
{{> customBuffEditList parentId=_id parentCollection="Items" charId=charId}}
</template>
<template name="containerDropdown">

View File

@@ -61,15 +61,15 @@
<template name="libraryItem">
<tr class="item library-item {{#if selected}}selected{{/if}}">
<td class="itemName">
{{item.name}}
{{itemName}}
<paper-ripple></paper-ripple>
</td>
<td>
{{item.weight}} lb.
{{itemWeight}} lb.
<paper-ripple></paper-ripple>
</td>
<td>
{{valueString item.value}}
{{valueString itemValue}}
<paper-ripple></paper-ripple>
</td>
</tr>

View File

@@ -107,3 +107,24 @@ Template.itemLibraryDialog.events({
template.searchTerm.set(value);
},
});
Template.libraryItem.helpers({
itemName: function(){
return this.item.libraryName || this.item.name;
},
itemWeight: function(){
if (this.item.quantity) {
return this.item.weight * this.item.quantity;
} else {
return this.item.weight;
}
},
itemValue: function(){
if (this.item.quantity) {
return this.item.value * this.item.quantity;
} else {
return this.item.value;
}
},
});

View File

@@ -12,7 +12,7 @@
<template name="spellDetails">
<div class="paper-font-body2">
Level {{level}} {{school}} {{#if ritual}}ritual{{/if}}, {{preparedString}}
{{schoolAndLevel}}{{#if ritual}} (ritual){{/if}}, {{preparedString}}
</div>
<div style="margin: 16px 0 16px 0;">
{{#if castingTime}}
@@ -38,6 +38,7 @@
</div>
<div>{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}</div>
{{> attacksViewList charId=charId parentId=_id}}
{{> customBuffViewList charId=charId parentId=_id}}
</template>
<template name="spellEdit">
@@ -111,10 +112,12 @@
</paper-checkbox>
</div>
<!--Description-->
<div class="description-input layout horizontal end">
<paper-textarea id="descriptionInput" label="Description" style="width: calc(100% - 24px)" value={{description}}></paper-textarea>
{{> textareaBracketSuffix}}
</div>
{{> customBuffEditList parentId=_id parentCollection="Spells" charId=charId}}
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name isSpell=true}}
</template>

View File

@@ -29,6 +29,13 @@ Template.spellDialog.events({
});
Template.spellDetails.helpers({
schoolAndLevel: function(){
if (this.level == 0) {
return this.school + " cantrip";
} else {
return "Level " + this.level + " " + this.school;
}
},
getComponents: function(){
var components = "";
if (this.components.concentration) components += "C";

View File

@@ -83,7 +83,9 @@
{{#each spells ../_id ../../_id}}
{{#if showSpell ../../_id}}
<div class="item-slot">
<div class="tall spell item layout horizontal center" data-id={{_id}}>
<div class="tall spell item layout horizontal center spellItem"
data-id={{_id}}
draggable={{canEditCharacter charId}}>
<iron-icon icon="social:whatshot"
style="color: {{hexColor color}};
margin-right: 16px;"

View File

@@ -253,8 +253,8 @@ Template.spells.events({
pushDialogStack({
template: "spellLibraryDialog",
element: event.currentTarget,
callback: (result) => {
if (!result) return;
callback: (resultArray) => {
if (!resultArray) return;
if (!listId){
listId = SpellLists.insert({
name: "New SpellList",
@@ -263,53 +263,57 @@ Template.spells.events({
attackBonus: "intelligenceMod + proficiencyBonus",
});
}
// Make the library spell into a regular spell
let spell = _.omit(result, "library", "attacks", "effects");
spell._id = spellId;
spell.charId = charId;
spell.parent = {
id: listId,
collection: "SpellLists",
};
spell.prepared = "prepared";
Spells.insert(spell);
// Copy over attacks and effects
_.each(result.attacks, (attack) => {
if (!("attackBonus" in attack)) {attack.attackBonus = "attackBonus"} //if no attack bonus provided, use spell list's
attack.charId = charId;
attack.parent = {id: spellId, collection: "Spells"};
Attacks.insert(attack);
});
_.each(result.effects, (effect) => {
effect.charId = charId;
effect.parent = {id: spellId, collection: "Spells"};
Effects.insert(effect);
});
/******[UNCOMMENT ONCE BUFFS ARE ADDED]*******
_.each(result.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
//loop through all returned spells
_.each(resultArray, (rawSpell, index) =>{
// Make the library spell into a regular spell
let spell = _.omit(rawSpell, "library", "attacks", "effects");
if (index == 0) {
spell._id = spellId; //only do this for the first spell added
}
spell.charId = charId;
spell.parent = {
id: listId,
collection: "SpellLists",
};
spell.prepared = "prepared";
Spells.insert(spell);
// Copy over attacks and effects
_.each(rawSpell.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"};
attack.parent = {id: spellId, collection: "Spells"};
Attacks.insert(attack);
});
_.each(buff.effects, (effect) => {
_.each(rawSpell.effects, (effect) => {
effect.charId = charId;
effect.parent = {id: buffId, collection: "Buffs"};
effect.parent = {id: spellId, collection: "Spells"};
Effects.insert(effect);
});
_.each(buff.proficiencies, (prof) => {
prof.charId = charId;
prof.parent = {id: buffId, collection: "Buffs"};
Proficiencies.insert(prof);
_.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);
});
});
});
*******[UNCOMMENT ONCE BUFFS ARE ADDED]******/
},
returnElement: () => $(`[data-id='${spellId}']`).get(0),
})
@@ -333,3 +337,49 @@ Template.spells.events({
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);
}
},
});

View File

@@ -1,7 +1,3 @@
.spell-library-dialog .spell.selected {
background-color: #e4e4e4;
}
.spell-library-dialog .category-header {
font-size: 16px;
}
@@ -13,11 +9,3 @@
.spell-library-dialog .category-header iron-icon.open {
transform: rotate(90deg);
}
.spell-library-dialog table {
border-collapse: collapse;
}
.spell-library-dialog .library-spell td {
position: relative;
}

View File

@@ -13,15 +13,18 @@
<div class="spells" style="padding:8px">
{{#if searchTerm}}
{{#if searchSpells.count}}
<table style="width: 100%">
<tbody>
{{#each spell in searchSpells}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</tbody>
</table>
<div>
{{#each spell in searchSpells}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</div>
{{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}}
{{#unless searchReady}}
<div class="layout vertical center" style="width: 100%; padding: 16px;">
@@ -36,13 +39,11 @@
{{name}}
</div>
<iron-collapse opened={{isOpen key}}>
<table style="width: 100%">
<tbody>
{{#each spell in (spellsInCategory key)}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</tbody>
</table>
<div>
{{#each spell in (spellsInCategory key)}}
{{>librarySpell spell=spell selected=(isSelected spell)}}
{{/each}}
</div>
{{#unless ready key}}
<paper-spinner active></paper-spinner>
{{/unless}}
@@ -59,10 +60,10 @@
</template>
<template name="librarySpell">
<tr class="spell library-spell {{#if selected}}selected{{/if}}">
<td class="spellName">
<div style="margin: ">
<paper-checkbox class="spell library-spell" checked={{selected}} style="padding: 2px 0 2px 16px; width: 100%;">
{{spell.name}}
<paper-ripple></paper-ripple>
</td>
</tr>
</paper-checkbox>
</div>
</template>

View File

@@ -14,7 +14,7 @@ const categories = [
];
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.categoriesOpen = new ReactiveVar([]);
this.readyDict = new ReactiveDict();
@@ -59,8 +59,12 @@ Template.spellLibraryDialog.helpers({
});
},
isSelected(spell){
const selected = Template.instance().selectedSpell.get();
return selected && selected._id === spell._id;
const selected = Template.instance().selectedSpells.get();
return _.contains(selected, spell._id);
},
selectedCount(){
const selected = Template.instance().selectedSpells.get();
return selected && selected.length;
},
isOpen(key){
const cats = Template.instance().categoriesOpen.get();
@@ -89,10 +93,26 @@ Template.spellLibraryDialog.events({
popDialogStack();
},
"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){
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){
popDialogStack();

View File

@@ -1,5 +1,5 @@
<!-- data just needs charId -->
<template name="addTHPDialog">
<template name="addEHPDialog">
<div class="fit layout vertical">
<app-header-layout has-scrolling-region class="new-character-dialog flex">
<app-header fixed effects="waterfall">

View File

@@ -1,8 +1,8 @@
Template.addTHPDialog.onRendered(function(){
Template.addEHPDialog.onRendered(function(){
this.find("#quantityInput").focus();
});
Template.addTHPDialog.events({
Template.addEHPDialog.events({
"tap .addButton": function(event, instance){
popDialogStack();
var max = +instance.find("#quantityInput").value;

View File

@@ -3,10 +3,15 @@
margin-right: 8px;
}
.healthCard .bottom-border {
border-bottom: rgba(0,0,0,0.24) solid 1px;
margin-bottom: 12px;
}
.healthCard #stableButton {
color: #b71c1c;
transition: color 0.4s ease;
width: 100%
width: 100%;
}
.healthCard #stableButton:before {

View File

@@ -5,33 +5,52 @@
Hit Points
</div>
<paper-icon-button class="white54"
id="addTempHP"
id="addExtraHP"
icon="add"
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
</paper-icon-button>
</div>
<div class="right flex layout vertical center-justified" style="min-width: 180px;">
<!-- main HP slider -->
<div class="layout horizontal">
<paper-diff-slider id="hitPointSlider"
editable pin
disabled={{#unless canEditCharacter _id}}true{{/unless}}>
</paper-diff-slider>
</div>
{{#each tempHitPoints}}
<div>
{{name}}
{{#if characterCalculate "attributeBase" _id "tempHP"}}
<!-- main THP slider -->
<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>
{{/if}}
{{#each extraHitPoints}}
<div class="layout horizontal center">
<div class="self-center">
{{name}}
</div>
<div style="height: 40px; width: 40px;">
{{#unless left}}
<paper-icon-button class="deleteTHP" icon="delete"></paper-icon-button>
<paper-icon-button class="deleteEHP" icon="delete"></paper-icon-button>
{{/unless}}
</div>
<paper-diff-slider class="tempHitPointSlider flex"
<paper-diff-slider class="extraHitPointSlider flex"
max={{maximum}}
value={{left}}
editable pin
></paper-diff-slider>
>
</paper-diff-slider>
</div>
{{/each}}
<div class="paper-font-caption">

View File

@@ -5,7 +5,7 @@ Template.healthCard.binding({
"#hitPointSlider": {
max: () => Characters.calculate.attributeBase(currentId() , "hitPoints"),
value: () => Characters.calculate.attributeValue(currentId() , "hitPoints"),
}
},
});
// 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;
if (oldId !== id){
this.find("#hitPointSlider").resetOldValue();
this.find("#temporaryHitPointSlider").resetOldValue();
oldId = id;
}
});
});
Template.healthCard.helpers({
tempHitPoints: function(){
extraHitPoints: function(){
return TemporaryHitPoints.find({charId: this._id});
},
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 used = this.maximum - value;
TemporaryHitPoints.update(this._id, {$set: {"used": used}});
},
"click .deleteTHP": function(event){
"click .deleteEHP": function(event){
TemporaryHitPoints.remove(this._id);
},
"click #addTempHP": function(event){
"click #addExtraHP": function(event){
pushDialogStack({
template: "addTHPDialog",
template: "addEHPDialog",
data: {charId: this._id},
element: event.currentTarget.parentElement,
});

View File

@@ -42,6 +42,34 @@
</div>
</paper-material>
</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-->
<div>
<paper-material class="card">

View File

@@ -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({
"click .stat-card": function(event, instance){
var charId = instance.data._id;
@@ -65,4 +77,17 @@ Template.stats.events({
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),
})
},
});

View File

@@ -0,0 +1,17 @@
.characterPicker .character-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.characterPicker .partyHead {
font-weight: 500;
}
.characterPicker .partyHead iron-icon {
transition: transform 0.3s ease;
}
.characterPicker .partyHead iron-icon.open {
transform: rotate(90deg);
}

View File

@@ -0,0 +1,34 @@
<template name="characterPicker">
<dicecloud-selector class="characterPicker" selected={{selected}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
{{#if selfId}}{{#if includeSelf}}
<paper-item class="short clickable" name={{selfId}}>
<div class="character-name">
Self
</div>
</paper-item>
{{/if}}{{/if}}
{{#each charactersWithNoParty}}
<paper-item class="short clickable" name={{_id}}>
<div class="character-name">
{{name}}
</div>
</paper-item>
{{/each}}
{{#each parties}}
<div class="paper-font-subhead partyHead clickable">
<iron-icon icon="chevron-right" class="{{#if isOpen _id}}open{{/if}}">
</iron-icon>
{{name}}
</div>
<iron-collapse opened={{isOpen _id}}>
{{#each charactersInParty}}
<paper-item class="short clickable" name={{_id}}>
<div class="character-name">
{{name}}
</div>
</paper-item>
{{/each}}
</iron-collapse>
{{/each}}
</dicecloud-selector>
</template>

View File

@@ -0,0 +1,53 @@
Template.characterPicker.onCreated(function() {
this.subscribe("characterList");
this.openedParties = new ReactiveVar(new Set());
});
Template.characterPicker.helpers({
parties() {
return Parties.find(
{owner: Meteor.userId()},
{sort: {name: 1}},
);
},
charactersInParty() {
var userId = Meteor.userId();
var selector = {
_id: {$in: this.characters, $ne: this.selfId},
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
};
if (this.writableOnly) {
selector.$or = [{writers: userId}, {owner: userId}];
}
return Characters.find(selector,{sort: {name: 1}});
},
charactersWithNoParty() {
var userId = Meteor.userId();
var charArrays = Parties.find({owner: userId}).map(p => p.characters);
var partyChars = _.uniq(_.flatten(charArrays));
var selector = {
_id: {$nin: partyChars, $ne: this.selfId},
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
};
if (this.writableOnly) {
selector.$or = [{writers: userId}, {owner: userId}];
}
return Characters.find(selector, {sort: {name: 1}});
},
isOpen(id) {
var openedParties = Template.instance().openedParties.get();
return openedParties.has(id);
},
});
Template.characterPicker.events({
"click .partyHead": function(event, instance){
var openedParties = instance.openedParties.get();
if (openedParties.has(this._id)){
openedParties.delete(this._id);
} else {
openedParties.add(this._id);
}
instance.openedParties.set(openedParties);
},
});

View File

@@ -34,7 +34,6 @@ Template.characterSideList.helpers({
},
isOpen(id) {
var openedParties = Template.instance().openedParties.get();
console.log(openedParties);
return openedParties.has(id);
},
});

View File

@@ -0,0 +1,12 @@
.base-dialog h1,
.base-dialog h2,
.base-dialog h3,
.base-dialog h4,
.base-dialog h5,
.base-dialog h6{
margin-bottom: 8px;
}
.base-dialog p{
margin-bottom: 20px;
}

View File

@@ -15,10 +15,12 @@
{{/unless}}
{{#unless hideColor}}
{{> colorDropdown}}
{{/unless}}
{{#unless editOnly}}
<paper-icon-button id="doneEditingButton"
icon="done">
</paper-icon-button>
{{/unless}}
<paper-icon-button id="doneEditingButton"
icon="done">
</paper-icon-button>
{{else}}
{{#if showEdit}}
<paper-icon-button id="editButton"

View File

@@ -1,71 +1,82 @@
statOrder = {
"strength": 1,
"dexterity": 2,
"constitution": 3,
"intelligence": 4,
"wisdom": 5,
"charisma": 6,
"strengthSave": 7,
"dexteritySave": 8,
"constitutionSave": 9,
"intelligenceSave": 10,
"wisdomSave": 11,
"charismaSave": 12,
"acrobatics": 13,
"animalHandling": 14,
"arcana": 15,
"athletics": 16,
"deception": 17,
"history": 18,
"insight": 19,
"intimidation": 20,
"investigation": 21,
"medicine": 22,
"nature": 23,
"perception": 24,
"performance": 25,
"persuasion": 26,
"religion": 27,
"sleightOfHand": 28,
"stealth": 29,
"survival": 30,
"initiative": 31,
"hitPoints": 32,
"armor": 33,
"dexterityArmor": 34,
"speed": 35,
"proficiencyBonus": 36,
"ki": 37,
"sorceryPoints": 38,
"rages": 39,
"rageDamage": 40,
"expertiseDice": 41,
"superiorityDice": 42,
"carryMultiplier": 43,
"level1SpellSlots": 44,
"level2SpellSlots": 45,
"level3SpellSlots": 46,
"level4SpellSlots": 47,
"level5SpellSlots": 48,
"level6SpellSlots": 49,
"level7SpellSlots": 50,
"level8SpellSlots": 51,
"level9SpellSlots": 52,
"d6HitDice": 53,
"d8HitDice": 54,
"d10HitDice": 55,
"d12HitDice": 56,
"acidMultiplier": 57,
"bludgeoningMultiplier": 58,
"coldMultiplier": 59,
"fireMultiplier": 60,
"forceMultiplier": 61,
"lightningMultiplier": 62,
"necroticMultiplier": 63,
"piercingMultiplier": 64,
"poisonMultiplier": 65,
"psychicMultiplier": 66,
"radiantMultiplier": 67,
"slashingMultiplier": 68,
"thunderMultiplier": 69,
};
var statsInOrder = [
"strength",
"dexterity",
"constitution",
"intelligence",
"wisdom",
"charisma",
"strengthSave",
"dexteritySave",
"constitutionSave",
"intelligenceSave",
"wisdomSave",
"charismaSave",
"acrobatics",
"animalHandling",
"arcana",
"athletics",
"deception",
"history",
"insight",
"intimidation",
"investigation",
"medicine",
"nature",
"perception",
"performance",
"persuasion",
"religion",
"sleightOfHand",
"stealth",
"survival",
"initiative",
"hitPoints",
"armor",
"dexterityArmor",
"speed",
"proficiencyBonus",
"ki",
"sorceryPoints",
"rages",
"rageDamage",
"expertiseDice",
"superiorityDice",
"carryMultiplier",
"level1SpellSlots",
"level2SpellSlots",
"level3SpellSlots",
"level4SpellSlots",
"level5SpellSlots",
"level6SpellSlots",
"level7SpellSlots",
"level8SpellSlots",
"level9SpellSlots",
"d6HitDice",
"d8HitDice",
"d10HitDice",
"d12HitDice",
"acidMultiplier",
"bludgeoningMultiplier",
"coldMultiplier",
"fireMultiplier",
"forceMultiplier",
"lightningMultiplier",
"necroticMultiplier",
"piercingMultiplier",
"poisonMultiplier",
"psychicMultiplier",
"radiantMultiplier",
"slashingMultiplier",
"thunderMultiplier",
];
statOrder = {};
_.each(statsInOrder, function(element, index){
statOrder[element] = index;
});

View File

@@ -0,0 +1,161 @@
characterExport = function(charId){
var char = Characters.findOne(charId);
if (!char) {
return {
error: charId + " character not found"
};
}
if (char.settings.viewPermission !== "public" && Meteor.isServer){
return {
error: charId + " character is not viewable to anyone with link"
};
}
var baseValue = function(attributeName){
return Characters.calculate.attributeBase(charId, attributeName);
};
var attributeValue = function(attributeName){
return Characters.calculate.attributeValue(charId, attributeName);
};
var skillMod = function(skillName){
return Characters.calculate.skillMod(charId, skillName);
};
var damageMods = getDamageMods(charId);
var character = {
"Id": char._id,
"Name": char.name,
"Source": "DiceCloud",
"Type": char.race,
"HPBase": baseValue("hitPoints"),
"HPValue": attributeValue("hitPoints"),
"HitDice": getHitDiceString(charId) || "",
"AC": attributeValue("armor"),
"Initiative": skillMod("initiative"),
"Speed": attributeValue("speed"),
"Str": attributeValue("strength"),
"Dex": attributeValue("dexterity"),
"Con": attributeValue("constitution"),
"Cha": attributeValue("charisma"),
"Int": attributeValue("intelligence"),
"Wis": attributeValue("wisdom"),
"DamageVulnerabilities": damageMods.vulnerabilities,
"DamageResistances": damageMods.resistances,
"DamageImmunities": damageMods.immunities,
"StrSave": skillMod("strengthSave"),
"DexSave": skillMod("dexteritySave"),
"ConSave": skillMod("constitutionSave"),
"IntSave": skillMod("intelligenceSave"),
"WisSave": skillMod("wisdomSave"),
"ChaSave": skillMod("charismaSave"),
"passivePerception": Characters.calculate.passiveSkill(charId, "perception"),
"Languages": getLanguages(charId),
"Description": char.description || "",
};
_.extend(character, getSkills(charId));
return character;
}
var getHitDiceString = function(charId){
var d6 = Characters.calculate.attributeBase(charId, "d6HitDice");
var d8 = Characters.calculate.attributeBase(charId, "d8HitDice");
var d10 = Characters.calculate.attributeBase(charId, "d10HitDice");
var d12 = Characters.calculate.attributeBase(charId, "d12HitDice");
var con = Characters.calculate.abilityMod(charId,"constitution");
var string = "" +
(d6 ? `${d6}d6 + ` : "") +
(d8 ? `${d8}d8 + ` : "") +
(d10 ? `${d10}d10 + ` : "") +
(d12 ? `${d12}d12 + ` : "") +
con;
}
var getArmorString = function(charId){
var bases = Effects.find({
charId: charId,
stat: "armor",
operation: "base",
enabled: true,
}).map(e => ({
ame: e.name,
value: evaluateEffect(charId, e),
}));
var base = bases.length && _.max(bases, b => b.value).name || "";
var effects = Effects.find({
charId: charId,
stat: "armor",
operation: {$ne: "base"},
enabled: true,
}).map(e => e.name);
var strings = base ? [base] : [];
strings = strings.concat(effects);
return strings.join(", ");
}
var getDamageMods = function(charId){
// jscs:disable maximumLineLength
var multipliers = [
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
];
// jscs:enable maximumLineLength
multipliers = _.groupBy(multipliers, "value");
var names = o => o.name;
return {
"immunities": _.map(multipliers["0"], names).join(", "),
"resistances": _.map(multipliers["0.5"], names).join(", "),
"vulnerabilities": _.map(multipliers["2"], names).join(", "),
};
}
var getSkills = function(charId){
var allSkills = [
{name: "acrobatics", attribute: "dexterity"},
{name: "animalHandling", attribute: "wisdom"},
{name: "arcana", attribute: "intelligence"},
{name: "athletics", attribute: "strength"},
{name: "deception", attribute: "charisma"},
{name: "history", attribute: "intelligence"},
{name: "insight", attribute: "wisdom"},
{name: "intimidation", attribute: "charisma"},
{name: "investigation", attribute: "intelligence"},
{name: "medicine", attribute: "wisdom"},
{name: "nature", attribute: "intelligence"},
{name: "perception", attribute: "wisdom"},
{name: "performance", attribute: "charisma"},
{name: "persuasion", attribute: "charisma"},
{name: "religion", attribute: "intelligence"},
{name: "sleightOfHand", attribute: "dexterity"},
{name: "stealth", attribute: "dexterity"},
{name: "survival", attribute: "wisdom"},
];
var skills = {};
_.each(allSkills, skill => {
var value = Characters.calculate.skillMod(charId, skill.name);
var mod = Characters.calculate.abilityMod(charId, skill.attribute);
if (value !== mod){
skills[skill.name] = value;
}
});
return skills;
};
var getLanguages = function(charId){
return Proficiencies.find({
charId,
enabled: true,
type: "language",
}).map(l => l.name).join(", ");
};

View File

@@ -0,0 +1,9 @@
vMixCharacter = function(charId){
return JSON.stringify([characterExport(charId)], null, 2);
};
vMixParty = function(partyId){
var party = Parties.findOne(partyId);
var chars = _.map(party.characters, charId => characterExport(charId));
return JSON.stringify(chars, null, 2);
};

View File

@@ -26,28 +26,32 @@ Meteor.methods({
var condition = getCondition(conditionName);
//create the buff
var buff = _.extend(
{charId: charId, type: "inate"}, condition.buff
{
charId: charId,
},
condition.buff
);
//make sure the character doesn't already have the buff
var existingBuffs = Buffs.find(_.clone(buff)).count();
var existingBuffs = Conditions.find(_.clone(buff)).count();
if (existingBuffs) return;
//remove exclusive conditions
_.each(condition.exclusiveConditions, function(exCond) {
Meteor.call("removeCondition", charId, exCond);
});
//insert the buff
var buffId = Buffs.insert(buff);
var buffId = Conditions.insert(buff);
//extend and insert each effect
_.each(condition.effects, function(effect) {
var newEffect = {
stat: effect.stat,
operation: effect.operation,
calculation: effect.calculation,
value: effect.value,
charId: charId,
parent: {
id: buffId,
collection: "Buffs",
collection: "Conditions",
},
enabled: true,
};
@@ -73,11 +77,26 @@ Meteor.methods({
var condition = getCondition(conditionName);
//remove the buff
var buff = _.extend(
{charId: charId, type: "inate"}, condition.buff
{charId: charId}, condition.buff
);
Buffs.remove(buff);
Conditions.remove(buff);
//dont remove the effects, they get removed automatically through parenting
},
getConditions: function() {
return Object.keys(CONDITIONS);
},
getConditionName: function(conditionName) {
//get condition from constant
var condition = CONDITIONS[conditionName];
//check that condition exists
if (!condition) {
throw new Meteor.Error(
"Invalid condition",
conditionName + " is not a known condition"
);
}
return condition.buff.name;
},
});
trackEncumbranceConditions = function(charId, templateInstance) {
@@ -150,7 +169,7 @@ CONDITIONS = {
{
stat: "perception",
operation: "conditional",
calculation: "You fail your perception check if it requires sight",
calculation: "You fail your Perception check if it requires sight",
}
],
},
@@ -164,7 +183,7 @@ CONDITIONS = {
{
stat: "perception",
operation: "conditional",
calculation: "You fail your perception check if it requires hearing",
calculation: "You fail your Perception check if it requires hearing",
}
],
},
@@ -207,7 +226,7 @@ CONDITIONS = {
paralyzed: {
buff: {
name: "Paralyzed",
description: "A paralyzed creature is incapacitated and cant move or speak.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
description: "A paralyzed creature is **incapacitated** and cant move or speak.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
},
effects: [
{
@@ -232,7 +251,7 @@ CONDITIONS = {
petrified: {
buff: {
name: "Petrified",
description: "A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.\n\nA petrified creature is incapacitated and cant move or speak, and is unaware of its surroundings.\n\nAttack rolls against the creature have advantage.\n\nThe creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
description: "A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.\n\nA petrified creature is **incapacitated** and cant move or speak, and is unaware of its surroundings.\n\nAttack rolls against the creature have advantage.\n\nThe creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
},
effects: (function() {
var effects = [
@@ -294,7 +313,7 @@ CONDITIONS = {
stunned: {
buff: {
name: "Stunned",
description: "A stunned creature is incapacitated, cant move, and can speak only falteringly\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.",
description: "A stunned creature is **incapacitated**, cant move, and can speak only falteringly\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.",
},
effects: [
{
@@ -317,7 +336,7 @@ CONDITIONS = {
unconscious: {
buff: {
name: "Unconscious",
description: "An unconscious creature is incapacitated, cant move or speak, and is unaware of its surroundings.\n\nThe creature drops whatever its holding and falls prone.\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
description: "An unconscious creature is **incapacitated**, cant move or speak, and is unaware of its surroundings.\n\nThe creature drops whatever its holding and falls **prone**.\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
},
subConditions: ["incapacitated", "prone"],
},

View File

@@ -0,0 +1,11 @@
Meteor.methods({
removeMeFromReaders: function(charId) {
var userId = Meteor.userId();
var character = Characters.findOne(charId);
if (!character) return;
if (!_.contains(character.readers, userId)) return;
Characters.update(charId, {$pull: {"readers": userId}}); //we don't check write permission as you should always be able to remove youself from readers
}
});

View File

@@ -60,4 +60,14 @@
color: rgba(255,255,255,0.87) !important;
}
}
.healthCard #temporaryHitPointSlider {
--paper-diff-slider-active-color: #009688; /* teal */
--paper-diff-slider-knob-color: #009688;
--paper-diff-slider-pin-color: #009688;
}
.healthCard .extraHitPointSlider {
--paper-diff-slider-active-color: #00BCD4; /* cyan */
--paper-diff-slider-knob-color: #00BCD4;
--paper-diff-slider-pin-color: #00BCD4;
}
</style>

View File

@@ -1,6 +1,6 @@
Meteor.startup(() => {
const collections = [
Attacks, Buffs, Classes, Effects, Experiences,
Attacks, Buffs, Classes, CustomBuffs, Effects, Experiences,
Features, Notes, Proficiencies, SpellLists, Spells,
Containers, Items,
];

View File

@@ -36,3 +36,21 @@ Migrations.add({
return;
},
});
Migrations.add({
version: 2,
name: "Adds TempHP as a character attribute",
up: function() {
//update characters
Characters.find({}).forEach(function(char){
if (char.tempHP) return;
Characters.update(char._id, {$set: {
"tempHP.adjustment": 0,
"tempHP.reset": "longRest",
}});
});
},
down: function(){
return;
},
});

View File

@@ -13,22 +13,40 @@ Meteor.publish("singleCharacter", function(characterId){
return [
Characters.find({_id: characterId}),
//get all the assets for this character including soft deleted ones
Actions.find ({charId: characterId}, {removed: true}),
Attacks.find ({charId: characterId}, {removed: true}),
Buffs.find ({charId: characterId}, {removed: true}),
Classes.find ({charId: characterId}, {removed: true}),
Containers.find ({charId: characterId}, {removed: true}),
Effects.find ({charId: characterId}, {removed: true}),
Experiences.find ({charId: characterId}, {removed: true}),
Features.find ({charId: characterId}, {removed: true}),
Items.find ({charId: characterId}, {removed: true}),
Notes.find ({charId: characterId}, {removed: true}),
Spells.find ({charId: characterId}, {removed: true}),
SpellLists.find ({charId: characterId}, {removed: true}),
TemporaryHitPoints.find({charId: characterId}, {removed: true}),
Proficiencies.find ({charId: characterId}, {removed: true}),
Actions.find ({charId: characterId}, {removed: true}),
Attacks.find ({charId: characterId}, {removed: true}),
Buffs.find ({charId: characterId}, {removed: true}),
Classes.find ({charId: characterId}, {removed: true}),
Conditions.find ({charId: characterId}, {removed: true}),
Containers.find ({charId: characterId}, {removed: true}),
CustomBuffs.find ({charId: characterId}, {removed: true}),
Effects.find ({charId: characterId}, {removed: true}),
Experiences.find ({charId: characterId}, {removed: true}),
Features.find ({charId: characterId}, {removed: true}),
Items.find ({charId: characterId}, {removed: true}),
Notes.find ({charId: characterId}, {removed: true}),
Spells.find ({charId: characterId}, {removed: true}),
SpellLists.find ({charId: characterId}, {removed: true}),
TemporaryHitPoints.find ({charId: characterId}, {removed: true}),
Proficiencies.find ({charId: characterId}, {removed: true}),
];
} else {
return [];
}
});
Meteor.publish("singleCharacterName", function(characterId){
userId = this.userId;
var char = Characters.findOne({
_id: characterId,
$or: [
{readers: userId},
{writers: userId},
{owner: userId},
{"settings.viewPermission": "public"},
],
});
if (char) {
return Characters.find(characterId, {fields:{"name": 1}});
}
});