Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2404845d51 | ||
|
|
bf032bcdf3 | ||
|
|
ff8ae89722 | ||
|
|
80ca7307ce | ||
|
|
a539b0bc6c | ||
|
|
c6b3cad9c8 | ||
|
|
95b7b66390 | ||
|
|
43c4122fe3 | ||
|
|
3f4dcc146a | ||
|
|
e4600decd0 | ||
|
|
f6df716870 | ||
|
|
b99da301cd | ||
|
|
0a01885300 | ||
|
|
5cb1515235 | ||
|
|
7430c2c795 | ||
|
|
39f7548b8d | ||
|
|
c4a8c4b7ba | ||
|
|
263aba596c |
@@ -20,3 +20,7 @@ mike:mocha
|
||||
dburles:mongo-collection-instances
|
||||
percolate:migrations
|
||||
ecwyne:mathjs
|
||||
useraccounts:polymer
|
||||
accounts-google
|
||||
splendido:accounts-meld
|
||||
email
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
accounts-base@1.2.0
|
||||
accounts-google@1.0.4
|
||||
accounts-oauth@1.1.5
|
||||
accounts-password@1.1.1
|
||||
accounts-ui@1.1.5
|
||||
accounts-ui-unstyled@1.1.7
|
||||
aldeed:collection2@2.3.3
|
||||
aldeed:simple-schema@1.3.2
|
||||
aldeed:simple-schema@1.3.3
|
||||
amplify@1.0.0
|
||||
autoupdate@1.2.1
|
||||
base64@1.0.3
|
||||
@@ -24,6 +26,7 @@ ejson@1.0.6
|
||||
email@1.0.6
|
||||
fastclick@1.0.3
|
||||
geojson-utils@1.0.3
|
||||
google@1.1.5
|
||||
html-tools@1.0.4
|
||||
htmljs@1.0.4
|
||||
http@1.1.0
|
||||
@@ -44,20 +47,22 @@ less@1.0.14
|
||||
livedata@1.0.13
|
||||
localstorage@1.0.3
|
||||
logging@1.0.7
|
||||
matb33:collection-hooks@0.7.11
|
||||
matb33:collection-hooks@0.7.13
|
||||
meteor@1.1.6
|
||||
meteor-platform@1.2.2
|
||||
mike:mocha@0.5.3
|
||||
mike:mocha@0.5.4
|
||||
minifiers@1.1.5
|
||||
minimongo@1.0.8
|
||||
mobile-status-bar@1.0.3
|
||||
momentjs:moment@2.10.3
|
||||
mongo@1.1.0
|
||||
npm-bcrypt@0.7.8_2
|
||||
oauth@1.1.4
|
||||
oauth2@1.1.3
|
||||
observe-sequence@1.0.6
|
||||
ordered-dict@1.0.3
|
||||
package-version-parser@3.0.3
|
||||
percolate:migrations@0.7.3
|
||||
percolate:migrations@0.7.5
|
||||
practicalmeteor:chai@1.9.2_3
|
||||
practicalmeteor:loglevel@1.1.0_3
|
||||
random@1.0.3
|
||||
@@ -72,16 +77,21 @@ sanjo:meteor-version@1.0.0
|
||||
service-configuration@1.0.4
|
||||
session@1.1.0
|
||||
sha@1.0.3
|
||||
softwarerero:accounts-t9n@1.0.9
|
||||
spacebars@1.0.6
|
||||
spacebars-compiler@1.0.6
|
||||
splendido:accounts-emails-field@1.2.0
|
||||
splendido:accounts-meld@1.3.0
|
||||
srp@1.0.3
|
||||
templating@1.1.1
|
||||
tracker@1.0.7
|
||||
ui@1.0.6
|
||||
underscore@1.0.3
|
||||
url@1.0.4
|
||||
useraccounts:core@1.9.1
|
||||
useraccounts:polymer@1.9.1
|
||||
velocity:chokidar@0.12.6_1
|
||||
velocity:core@0.6.0
|
||||
velocity:core@0.6.1
|
||||
velocity:html-reporter@0.5.3
|
||||
velocity:meteor-internals@1.1.0_7
|
||||
velocity:shim@0.1.0
|
||||
|
||||
@@ -61,7 +61,6 @@ Effects.attachSchema(Schemas.Effect);
|
||||
if (Meteor.isServer) Characters.after.insert(function(userId, char) {
|
||||
Effects.insert({
|
||||
charId: char._id,
|
||||
type: "inate",
|
||||
name: "Constitution modifier for each level",
|
||||
stat: "hitPoints",
|
||||
operation: "add",
|
||||
@@ -69,11 +68,11 @@ if (Meteor.isServer) Characters.after.insert(function(userId, char) {
|
||||
parent: {
|
||||
id: char._id,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.insert({
|
||||
charId: char._id,
|
||||
type: "inate",
|
||||
name: "Proficiency bonus by level",
|
||||
stat: "proficiencyBonus",
|
||||
operation: "add",
|
||||
@@ -81,11 +80,11 @@ if (Meteor.isServer) Characters.after.insert(function(userId, char) {
|
||||
parent: {
|
||||
id: char._id,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.insert({
|
||||
charId: char._id,
|
||||
type: "inate",
|
||||
name: "Dexterity Armor Bonus",
|
||||
stat: "armor",
|
||||
operation: "add",
|
||||
@@ -93,11 +92,11 @@ if (Meteor.isServer) Characters.after.insert(function(userId, char) {
|
||||
parent: {
|
||||
id: char._id,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
Effects.insert({
|
||||
charId: char._id,
|
||||
type: "inate",
|
||||
name: "Natural Armor",
|
||||
stat: "armor",
|
||||
operation: "base",
|
||||
@@ -105,6 +104,7 @@ if (Meteor.isServer) Characters.after.insert(function(userId, char) {
|
||||
parent: {
|
||||
id: char._id,
|
||||
collection: "Characters",
|
||||
group: "Inate",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,156 @@ Schemas.Item = new SimpleSchema({
|
||||
|
||||
Items.attachSchema(Schemas.Item);
|
||||
|
||||
var checkMovePermission = function(itemId, parent) {
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item)
|
||||
throw new Meteor.Error("No such item",
|
||||
"An item could not be found to move");
|
||||
//handle permissions
|
||||
var permission = Meteor.call("canWriteCharacter", item.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items from this character");
|
||||
}
|
||||
if (parent.collection === "Characters"){
|
||||
permission = Meteor.call("canWriteCharacter", parent.id);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items to this character");
|
||||
}
|
||||
} else {
|
||||
var parentCollectionObject = global[parent.collection];
|
||||
var parentObject = null;
|
||||
if (parentCollectionObject)
|
||||
parentObject = parentCollectionObject.findOne(
|
||||
parent.id, {fields: {_id: 1, charId: 1}}
|
||||
);
|
||||
if (!parentObject) throw new Meteor.Error(
|
||||
"Invalid parent",
|
||||
"The destination parent " + parent.id +
|
||||
" does not exist in the collection " + parent.collection
|
||||
);
|
||||
if (parentObject.charId){
|
||||
permission = Meteor.call("canWriteCharacter", parentObject.charId);
|
||||
if (!permission){
|
||||
throw new Meteor.Error("Access denied",
|
||||
"Not permitted to move items to this character");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var moveItem = function(itemId, enable, parentCollection, parentId) {
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item) return;
|
||||
parentCollection = parentCollection || item.parent.collection;
|
||||
parentId = parentId || item.parent.id;
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(itemId, {collection: parentCollection, id: parentId});
|
||||
}
|
||||
|
||||
//update the item provided the update will actually change something
|
||||
if (
|
||||
item.parent.collection !== parentCollection ||
|
||||
item.parent.id !== parentId ||
|
||||
item.enabled !== enable
|
||||
){
|
||||
Items.update(
|
||||
itemId,
|
||||
{$set: {
|
||||
"parent.collection": parentCollection,
|
||||
"parent.id": parentId,
|
||||
enabled: enable,
|
||||
}}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Meteor.methods({
|
||||
moveItemToParent: function(itemId, parent) {
|
||||
check(itemId, String);
|
||||
check(parent, {collection: String, id: String});
|
||||
moveItem(itemId, false, parent.collection, parent.id);
|
||||
},
|
||||
moveItemToCharacter: function(itemId, charId) {
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, false, "Characters", charId);
|
||||
},
|
||||
moveItemToContainer: function(itemId, containerId) {
|
||||
check(itemId, String);
|
||||
check(containerId, String);
|
||||
moveItem(itemId, false, "Containers", containerId);
|
||||
},
|
||||
equipItem: function(itemId, charId){
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, true, "Characters", charId);
|
||||
},
|
||||
unequipItem: function(itemId, charId){
|
||||
check(itemId, String);
|
||||
check(charId, String);
|
||||
moveItem(itemId, false, "Characters", charId);
|
||||
},
|
||||
splitItemToParent: function(itemId, moveQuantity, parent){
|
||||
check(itemId, String);
|
||||
check(moveQuantity, Number);
|
||||
check(parent, {id: String, collection: String});
|
||||
|
||||
//get the item
|
||||
var item = Items.findOne(itemId);
|
||||
if (!item) return;
|
||||
|
||||
//don't bother moving nothing
|
||||
if (moveQuantity <= 0 || item.quantity <= 0){
|
||||
return;
|
||||
}
|
||||
//ensure we are only moving up to the current stack size
|
||||
if (item.quantity < moveQuantity){
|
||||
moveQuantity = this.quantity;
|
||||
}
|
||||
|
||||
if (Meteor.isServer) {
|
||||
checkMovePermission(itemId, parent);
|
||||
}
|
||||
|
||||
//create a new item stack
|
||||
var newStack = _.omit(EJSON.clone(item), "_id");
|
||||
newStack.parent = parent;
|
||||
newStack.quantity = moveQuantity;
|
||||
|
||||
//find out if we have an exact replica in the destination
|
||||
var query = _.omit(newStack, ["parent", "quantity"]);
|
||||
query["parent.collection"] = newStack.parent.collection;
|
||||
query["parent.id"] = newStack.parent.id;
|
||||
query._id = {$ne: itemId}; //make sure we don't join it to itself
|
||||
var existingStack = Items.findOne(query);
|
||||
if (existingStack){
|
||||
//increase the existing stack's size
|
||||
Items.update(
|
||||
existingStack._id,
|
||||
{$inc: {quantity: moveQuantity}}
|
||||
);
|
||||
} else {
|
||||
//insert the new stack
|
||||
Items.insert(newStack, function(err, id){
|
||||
if (err) throw err;
|
||||
//copy the children also
|
||||
Meteor.call("cloneChildren", item._id, {collection: "Items", id: id});
|
||||
});
|
||||
}
|
||||
|
||||
//reduce the old stack's size
|
||||
var oldQuantity = item.quantity - moveQuantity;
|
||||
if (oldQuantity === 0){
|
||||
Items.remove(itemId);
|
||||
} else {
|
||||
Items.update(itemId, {$set: {quantity: oldQuantity}});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Items.helpers({
|
||||
totalValue: function(){
|
||||
return this.value * this.quantity;
|
||||
@@ -33,103 +183,6 @@ Items.helpers({
|
||||
return this.name;
|
||||
}
|
||||
},
|
||||
equip: function(characterId){
|
||||
var charId = characterId || this.charId;
|
||||
if (!charId || !Characters.findOne(charId)) throw "Invalid character";
|
||||
if (this.parent.collection === "Characters" &&
|
||||
this.parent.id === charId &&
|
||||
this.enabled) {
|
||||
return;
|
||||
}
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Characters",
|
||||
"parent.id": charId,
|
||||
enabled: true,
|
||||
}}
|
||||
);
|
||||
},
|
||||
unequip: function(){
|
||||
if (!this.enabled) return;
|
||||
Items.update(this._id, {$set: {enabled: false}});
|
||||
},
|
||||
moveToContainer: function(containerId){
|
||||
if (!containerId || !Containers.findOne(containerId)){
|
||||
throw "Invalid container";
|
||||
}
|
||||
if (this.parent.collection === "Containers" &&
|
||||
this.parent.id === containerId &&
|
||||
!this.enabled) return;
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Containers",
|
||||
"parent.id": containerId,
|
||||
enabled: false,
|
||||
}}
|
||||
);
|
||||
},
|
||||
moveToCharacter: function(characterId){
|
||||
if (!characterId || !Characters.findOne(characterId)) {
|
||||
throw "Invalid character";
|
||||
}
|
||||
if (this.parent.collection === "Characters" &&
|
||||
this.parent.id === characterId &&
|
||||
!this.enabled) return;
|
||||
Items.update(
|
||||
this._id,
|
||||
{$set: {
|
||||
"parent.collection": "Characters",
|
||||
"parent.id": characterId,
|
||||
charId: characterId,
|
||||
enabled: false,
|
||||
}}
|
||||
);
|
||||
},
|
||||
splitToParent: function(parent, moveQuantity){
|
||||
check(parent, {id: String, collection: String});
|
||||
check(moveQuantity, Number);
|
||||
var parentCollection = Meteor.isClient ?
|
||||
window[parent.collection] : global[parent.collection];
|
||||
if (!parent.id || !parentCollection.findOne(parent.id)){
|
||||
throw "Invalid parent";
|
||||
}
|
||||
var oldStack = this;
|
||||
//we can only move as much as we have, leaving 0 behind at worst
|
||||
if (oldStack.quantity < moveQuantity) moveQuantity = oldStack.quantity;
|
||||
var oldQuantity = oldStack.quantity - moveQuantity;
|
||||
|
||||
var newStack = _.pick(oldStack, Schemas.Item.objectKeys());
|
||||
newStack.parent = parent;
|
||||
newStack.quantity = moveQuantity;
|
||||
|
||||
var existingStack = Items.findOne(_.omit(newStack, "quantity"));
|
||||
var updateStackSize = function(){
|
||||
if (oldQuantity > 0){
|
||||
Items.update(oldStack._id, {$set: {quantity: oldQuantity}});
|
||||
} else {
|
||||
Items.remove(oldStack._id);
|
||||
}
|
||||
};
|
||||
if (existingStack){
|
||||
Items.update(
|
||||
existingStack._id,
|
||||
{$inc: {quantity: moveQuantity}},
|
||||
{},
|
||||
function(){
|
||||
updateStackSize();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Items.insert(newStack, function(err, id){
|
||||
if (err) throw err;
|
||||
updateStackSize();
|
||||
//copy the children also
|
||||
Meteor.call("cloneChildren", oldStack._id, {collection: "Items", id: id});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Items.before.update(function(userId, doc, fieldNames, modifier, options){
|
||||
|
||||
27
rpg-docs/Model/Meta/ChangeLogs.js
Normal file
27
rpg-docs/Model/Meta/ChangeLogs.js
Normal file
@@ -0,0 +1,27 @@
|
||||
ChangeLogs = new Mongo.Collection("changeLogs");
|
||||
|
||||
Schemas.ChangeLog = new SimpleSchema({
|
||||
version: {
|
||||
type: String,
|
||||
},
|
||||
changes: {
|
||||
type: [String],
|
||||
},
|
||||
});
|
||||
|
||||
ChangeLogs.attachSchema(Schemas.ChangeLog);
|
||||
|
||||
ChangeLogs.allow({
|
||||
insert: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
update: function(userId, doc, fields, modifier) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
remove: function(userId, doc) {
|
||||
var user = Meteor.users.findOne(userId);
|
||||
if (user) return _.contains(user.roles, "admin");
|
||||
},
|
||||
});
|
||||
60
rpg-docs/Model/Meta/Reports.js
Normal file
60
rpg-docs/Model/Meta/Reports.js
Normal file
@@ -0,0 +1,60 @@
|
||||
Reports = new Mongo.Collection("reports");
|
||||
|
||||
Schemas.Report = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: false,
|
||||
optional: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
allowedValues: ["bug", "change", "feature", "general"],
|
||||
defaultValue: "bug",
|
||||
},
|
||||
//the immediate impact of doing this action (eg. -1 rages)
|
||||
severity: {
|
||||
type: Number,
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
metaData: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
});
|
||||
|
||||
Reports.attachSchema(Schemas.Report);
|
||||
|
||||
Meteor.methods({
|
||||
insertReport: function(report) {
|
||||
check(report, {
|
||||
title: String,
|
||||
description: String,
|
||||
type: String,
|
||||
severity: Number,
|
||||
metaData: Object,
|
||||
});
|
||||
report.owner = this.userId;
|
||||
Reports.insert(report);
|
||||
},
|
||||
deleteReport: function(id) {
|
||||
var user = Meteor.users.findOne(this.userId);
|
||||
if (!_.contains(user.roles, "admin")){
|
||||
throw new Meteor.Error(
|
||||
"not admin",
|
||||
"The user must be an administrator to delete feedback"
|
||||
);
|
||||
}
|
||||
Reports.remove(id);
|
||||
},
|
||||
});
|
||||
@@ -1,44 +1,23 @@
|
||||
Schema = {};
|
||||
|
||||
Schema.User = new SimpleSchema({
|
||||
username: {
|
||||
type: String,
|
||||
regEx: /^[a-z0-9A-Z_]{3,15}$/,
|
||||
optional: true,
|
||||
},
|
||||
emails: {
|
||||
type: [Object],
|
||||
// this must be optional if you also use other login services like facebook,
|
||||
// but if you use only accounts-password, then it can be required
|
||||
optional: true,
|
||||
},
|
||||
"emails.$.address": {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Email,
|
||||
},
|
||||
"emails.$.verified": {
|
||||
type: Boolean
|
||||
},
|
||||
createdAt: {
|
||||
type: Date
|
||||
},
|
||||
services: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
blackbox: true,
|
||||
},
|
||||
roles: {
|
||||
type: [String],
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.users.attachSchema(Schema.User);
|
||||
|
||||
Meteor.users.allow({
|
||||
update: function(userId, doc, fields, modifier) {
|
||||
return userId === doc._id &&
|
||||
fields.length === 1 &&
|
||||
fields[0] === "username";
|
||||
if (
|
||||
doc._id === userId &&
|
||||
_.contains(fields, "username") &&
|
||||
_.contains(fields, "profile") &&
|
||||
fields.length === 2 &&
|
||||
_.keys(modifier).length === 1 &&
|
||||
modifier.$set &&
|
||||
modifier.$set["profile.username"] &&
|
||||
modifier.$set.username &&
|
||||
_.keys(modifier.$set).length === 2
|
||||
){
|
||||
var expectedUsername = modifier.$set["profile.username"];
|
||||
expectedUsername = expectedUsername.toLowerCase().replace(/\s+/gm, "");
|
||||
if (modifier.$set.username !== expectedUsername){
|
||||
return false;
|
||||
}
|
||||
var foundUser = Meteor.call("getUserId", expectedUsername);
|
||||
return !foundUser || foundUser === userId;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,9 +3,29 @@ Router.configure({
|
||||
layoutTemplate: "layout",
|
||||
});
|
||||
|
||||
Router.plugin("ensureSignedIn", {
|
||||
except: [
|
||||
"home",
|
||||
"atSignIn",
|
||||
"atSignUp",
|
||||
"atForgotPassword",
|
||||
"atResetPwd",
|
||||
"atEnrollAccount",
|
||||
"atVerifyEmail",
|
||||
"atresendVerificationEmail",
|
||||
"loginButtons",
|
||||
"notFound",
|
||||
]
|
||||
});
|
||||
|
||||
Router.plugin("dataNotFound", {notFoundTemplate: "notFound"});
|
||||
|
||||
Router.map(function() {
|
||||
this.route("/", {
|
||||
name: "home",
|
||||
onAfterAction: function() {
|
||||
document.title = appName;
|
||||
},
|
||||
});
|
||||
|
||||
this.route("characterList", {
|
||||
@@ -33,7 +53,7 @@ Router.map(function() {
|
||||
data: function() {
|
||||
var data = Characters.findOne(
|
||||
{_id: this.params._id},
|
||||
{fields: {_id: 1, name: 1, color: 1}}
|
||||
{fields: {_id: 1, name: 1, color: 1, writers: 1, readers: 1}}
|
||||
);
|
||||
return data;
|
||||
},
|
||||
@@ -56,4 +76,21 @@ Router.map(function() {
|
||||
document.title = appName + " Account";
|
||||
},
|
||||
});
|
||||
|
||||
this.route("/changelog", {
|
||||
name: "changeLog",
|
||||
waitOn: function() {
|
||||
return [
|
||||
Meteor.subscribe("changeLog"),
|
||||
]
|
||||
},
|
||||
data: {
|
||||
changeLogs: function() {
|
||||
return ChangeLogs.find({}, {sort: {version: -1}});
|
||||
}
|
||||
},
|
||||
onAfterAction: function() {
|
||||
document.title = appName;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
6
rpg-docs/client/globalHelpers/canEditCharacter.js
Normal file
6
rpg-docs/client/globalHelpers/canEditCharacter.js
Normal file
@@ -0,0 +1,6 @@
|
||||
Template.registerHelper("canEditCharacter", function(charId) {
|
||||
var char = Characters.findOne(charId)
|
||||
var userId = Meteor.userId();
|
||||
return char.owner === userId ||
|
||||
_.contains(char.writers, userId);
|
||||
});
|
||||
@@ -5,18 +5,24 @@
|
||||
<div flex>
|
||||
{{name}}
|
||||
</div>
|
||||
<div>
|
||||
{{> colorDropdown}}
|
||||
</div>
|
||||
<paper-menu-button>
|
||||
<paper-icon-button icon="more-vert" noink></paper-icon-button>
|
||||
<paper-dropdown class="dropdown" halign="right">
|
||||
<core-menu class="menu" style="color: black; color: rgba(0,0,0,0.87);">
|
||||
<paper-item id="deleteCharacter"><core-icon icon="delete"></core-icon>Delete</paper-item>
|
||||
<paper-item id="shareCharacter"><core-icon icon="social:share"></core-icon>Share</paper-item>
|
||||
</core-menu>
|
||||
</paper-dropdown>
|
||||
</paper-menu-button>
|
||||
{{#if canEditCharacter _id}}
|
||||
<div>
|
||||
{{> colorDropdown}}
|
||||
</div>
|
||||
<paper-menu-button>
|
||||
<paper-icon-button icon="more-vert" noink></paper-icon-button>
|
||||
<paper-dropdown class="dropdown" halign="right">
|
||||
<core-menu class="menu" style="color: black; color: rgba(0,0,0,0.87);">
|
||||
<paper-item id="deleteCharacter">
|
||||
<core-icon icon="delete"></core-icon>Delete
|
||||
</paper-item>
|
||||
<paper-item id="shareCharacter">
|
||||
<core-icon icon="social:share"></core-icon>Share
|
||||
</paper-item>
|
||||
</core-menu>
|
||||
</paper-dropdown>
|
||||
</paper-menu-button>
|
||||
{{/if}}
|
||||
<div class="bottom fit" horizontal layout>
|
||||
<paper-tabs flex horizontal center layout id="characterSheetTabs" selected={{selectedTab}} class="{{colorClass}}">
|
||||
<paper-tab name="stats">Stats</paper-tab>
|
||||
|
||||
@@ -13,7 +13,7 @@ var getTab = function(charId){
|
||||
Template.characterSheet.helpers({
|
||||
selectedTab: function(){
|
||||
return getTab(this._id);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
Template.characterSheet.events({
|
||||
|
||||
@@ -104,14 +104,16 @@
|
||||
</div>
|
||||
<div class="fab-buffer"></div>
|
||||
</div>
|
||||
<paper-fab id="addFeature"
|
||||
class="floatyButton"
|
||||
icon="add"
|
||||
title="Add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Add"
|
||||
hero-id="main"></paper-fab>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab id="addFeature"
|
||||
class="floatyButton"
|
||||
icon="add"
|
||||
title="Add"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Add"
|
||||
hero-id="main"></paper-fab>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -77,10 +77,12 @@
|
||||
</div>
|
||||
<div class="fab-buffer"></div>
|
||||
</div>
|
||||
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3">
|
||||
<paper-fab-menu-item id="addItem" icon="note-add" color="#d23f31" tooltip="Item"></paper-fab-menu-item>
|
||||
<paper-fab-menu-item id="addContainer" icon="work" color="#d23f31" tooltip="Container"></paper-fab-menu-item>
|
||||
</paper-fab-menu>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3">
|
||||
<paper-fab-menu-item id="addItem" icon="note-add" color="#d23f31" tooltip="Item"></paper-fab-menu-item>
|
||||
<paper-fab-menu-item id="addContainer" icon="work" color="#d23f31" tooltip="Container"></paper-fab-menu-item>
|
||||
</paper-fab-menu>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -174,71 +174,93 @@ Template.inventoryItem.helpers({
|
||||
|
||||
Template.layout.events({
|
||||
"dragstart .inventoryItem": function(event, instance){
|
||||
event.originalEvent.dataTransfer.setData("dicecloud-id/items", this._id);
|
||||
Session.set("inventory.dragItemId", this._id);
|
||||
Session.set("inventory.dragItemOriginalContainer", this.container);
|
||||
Session.set("inventory.dragItemOriginalCharacter", this.charId);
|
||||
},
|
||||
"dragover .itemContainer, dragenter .itemContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .equipmentContainer, dragenter .equipmentContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .carriedContainer, dragenter .carriedContainer":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragover .characterRepresentative, dragenter .characterRepresentative":
|
||||
function(event, instance){
|
||||
if (_.contains(event.originalEvent.dataTransfer.types, "dicecloud-id/items")){
|
||||
event.preventDefault();
|
||||
}
|
||||
},
|
||||
"dragend .inventoryItem": function(event, instance){
|
||||
resetInvetorySession(); //this is a valid drop zone
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"dragover .itemContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"dragover .equipmentContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"dragover .carriedContainer": function(event, instance){
|
||||
event.preventDefault();
|
||||
},
|
||||
"drop .itemContainer": function(event, instacne){
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
"drop .itemContainer": function(event, instance){
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
GlobalUI.showDialog({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: item._id,
|
||||
id: itemId,
|
||||
parentCollection: "Containers",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the container
|
||||
item.moveToContainer(this._id);
|
||||
Meteor.call("moveItemToContainer", itemId, this._id);
|
||||
}
|
||||
resetInvetorySession();
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .equipmentContainer": function(event, instance){
|
||||
var charId = Session.get("inventory.dragItemOriginalCharacter");
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
item.equip(charId);
|
||||
resetInvetorySession();
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
Meteor.call("equipItem", itemId, this._id);
|
||||
Session.set("inventory.dragItemId", null);
|
||||
},
|
||||
"drop .carriedContainer": function(event, instance){
|
||||
var charId = Session.get("inventory.dragItemOriginalCharacter");
|
||||
var item = Items.findOne(Session.get("inventory.dragItemId"));
|
||||
var itemId = event.originalEvent.dataTransfer.getData("dicecloud-id/items");
|
||||
if (event.ctrlKey){
|
||||
//split the stack to the container
|
||||
GlobalUI.showDialog({
|
||||
template: "splitStackDialog",
|
||||
data: {
|
||||
id: item._id,
|
||||
id: itemId,
|
||||
parentCollection: "Characters",
|
||||
parentId: this._id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
//move item to the character
|
||||
item.moveToCharacter(this._id);
|
||||
Meteor.call("moveItemToCharacter", itemId, this._id);
|
||||
}
|
||||
resetInvetorySession();
|
||||
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
|
||||
GlobalUI.showDialog({
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
var resetInvetorySession = function(){
|
||||
_.defer(function(){
|
||||
Session.set("inventory.dragItemId", null);
|
||||
Session.set("inventory.dragItemOriginalContainer", null);
|
||||
Session.set("inventory.dragItemOriginalCharacter", null);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<template name="itemDetails">
|
||||
<div layout horizontal wrap center justified class="headline">
|
||||
{{#if weight}}<div class="sideMargin">{{totalWeight}}lbs</div>{{/if}}
|
||||
{{#if weight}}<div class="sideMargin">{{round totalWeight}}lbs</div>{{/if}}
|
||||
{{#if value}}<div>{{valueString totalValue}}</div>{{/if}}
|
||||
</div>
|
||||
<div layout horizontal wrap class="caption">
|
||||
|
||||
@@ -81,13 +81,10 @@ Template.itemEdit.events({
|
||||
},
|
||||
"change #equippedInput": function(event){
|
||||
var equipped = Template.instance().find("#equippedInput").checked;
|
||||
var item = Items.findOne(this._id);
|
||||
if (item){
|
||||
if (equipped){
|
||||
item.equip();
|
||||
} else {
|
||||
item.unequip();
|
||||
}
|
||||
if (equipped){
|
||||
Meteor.call("equipItem", this._id, this.charId);
|
||||
} else {
|
||||
Meteor.call("unequipItem", this._id, this.charId);
|
||||
}
|
||||
},
|
||||
"change #attunementCheckbox": function(event){
|
||||
@@ -107,7 +104,6 @@ Template.containerDropdown.events({
|
||||
var detail = event.originalEvent.detail;
|
||||
if (!detail.isSelected) return;
|
||||
var containerId = detail.item.getAttribute("name");
|
||||
var item = Items.findOne(Template.currentData()._id);
|
||||
item.moveToContainer(containerId);
|
||||
Meteor.call("moveItemToContainer", Template.currentData()._id, containerId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,13 +7,12 @@ Template.splitStackDialog.helpers({
|
||||
|
||||
Template.splitStackDialog.events({
|
||||
"tap #moveButton": function(event, instance){
|
||||
var item = Items.findOne(this.id);
|
||||
if (item){
|
||||
item.splitToParent(
|
||||
{collection: this.parentCollection , id: this.parentId},
|
||||
+instance.find("#quantityInput").value
|
||||
);
|
||||
}
|
||||
Meteor.call(
|
||||
"splitItemToParent",
|
||||
this.id,
|
||||
+instance.find("#quantityInput").value,
|
||||
{collection: this.parentCollection , id: this.parentId}
|
||||
);
|
||||
},
|
||||
"tap #oneButton":function(event, instance){
|
||||
instance.find("#quantityInput").value = 1;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template name="experienceDialog">
|
||||
{{#with experience}}
|
||||
{{#baseDialog title=name class=colorClass hideColor="true" startEditing=../startEditing}}
|
||||
<div horizontal layout center-justified>
|
||||
<div horizontal layout center-justified class= "display2">
|
||||
{{value}}
|
||||
</div>
|
||||
{{#if description}}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
<div class="fab-buffer"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab id="addNote"
|
||||
class="floatyButton"
|
||||
icon="add"
|
||||
@@ -75,4 +76,5 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
hero-id="main"></paper-fab>
|
||||
{{/if}}
|
||||
</template>
|
||||
@@ -1,5 +1,8 @@
|
||||
<template name="raceDialog">
|
||||
{{#baseDialog title="Race" class=colorClass hideColor="true" hideDelete="true" startEditing=startEditing}}
|
||||
<div horizontal layout center-justified class= "display2">
|
||||
{{race}}
|
||||
</div>
|
||||
{{> effectsViewList charId=charId parentId=charId parentGroup="racial"}}
|
||||
{{> proficiencyViewList charId=charId parentId=charId parentGroup="racial"}}
|
||||
{{else}}
|
||||
|
||||
@@ -91,8 +91,10 @@
|
||||
<div class="fab-buffer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3">
|
||||
<paper-fab-menu-item id="addSpell" icon="note-add" color="#d23f31" tooltip="Spell"></paper-fab-menu-item>
|
||||
<paper-fab-menu-item id="addSpellList" icon="work" color="#d23f31" tooltip="Spell List"></paper-fab-menu-item>
|
||||
</paper-fab-menu>
|
||||
{{#if canEditCharacter _id}}
|
||||
<paper-fab-menu id="inventoryAddMenu" icon="add" closeIcon="close" duration="0.3">
|
||||
<paper-fab-menu-item id="addSpell" icon="note-add" color="#d23f31" tooltip="Spell"></paper-fab-menu-item>
|
||||
<paper-fab-menu-item id="addSpellList" icon="work" color="#d23f31" tooltip="Spell List"></paper-fab-menu-item>
|
||||
</paper-fab-menu>
|
||||
{{/if}}
|
||||
</template>
|
||||
@@ -140,7 +140,12 @@ Template.attributeDialogView.helpers({
|
||||
return char.attributeValue(this.statName);
|
||||
},
|
||||
sourceName: function(){
|
||||
if (this.parent.collection === "Characters") return this.name;
|
||||
if (this.parent.group === "racial"){
|
||||
return this.getParent().race;
|
||||
}
|
||||
if (this.parent.collection === "Characters"){
|
||||
return this.name;
|
||||
}
|
||||
return this.getParent().name;
|
||||
},
|
||||
operationName: function(){
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
color: black;
|
||||
color: rgba(0, 0, 0, 0.870588);
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
padding: 12px 0 12px 16px;
|
||||
padding: 8px 0 8px 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.singleLineItem core-icon {
|
||||
height: 8px;
|
||||
margin-right: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
@@ -3,7 +3,11 @@
|
||||
{{#if characters.count}}
|
||||
<div>
|
||||
{{#each characters}}
|
||||
<div class="singleLineItem">{{name}}</div>
|
||||
<div class="singleLineItem characterRepresentative"
|
||||
layout horizontal center>
|
||||
<core-icon icon="image:brightness-1"></core-icon>
|
||||
<div>{{name}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
30
rpg-docs/client/views/feedback/feedback.html
Normal file
30
rpg-docs/client/views/feedback/feedback.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<template name="feedback">
|
||||
<div class="feedback" style="min-width: 300px; min-height: 370px">
|
||||
<div>
|
||||
<paper-input id="feedbackTitle" label="Title" floatinglabel></paper-input>
|
||||
</div>
|
||||
<div>
|
||||
<paper-dropdown-menu class="typeDropdown" label="Operation" flex>
|
||||
<paper-dropdown layered class="dropdown">
|
||||
<core-menu class="menu typeMenu" selected="general">
|
||||
<paper-item name="general">General Feedback</paper-item>
|
||||
<paper-item name="bug">Bug</paper-item>
|
||||
<paper-item name="change">Suggested Change</paper-item>
|
||||
<paper-item name="feature">Feature Request</paper-item>
|
||||
</core-menu>
|
||||
</paper-dropdown>
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div layout horizontal center>
|
||||
<div>Importance</div>
|
||||
<paper-slider id="severity" max=10 min=1 value=5 snap></paper-slider>
|
||||
</div>
|
||||
<paper-input-decorator label="Description" floatinglabel layout vertical>
|
||||
<paper-autogrow-textarea rows=10 maxRows=10>
|
||||
<textarea id="feedbackDescription"></textarea>
|
||||
</paper-autogrow-textarea>
|
||||
</paper-input-decorator>
|
||||
</div>
|
||||
<paper-button id="cancelButton" affirmative>Cancel</paper-button>
|
||||
<paper-button id="sendButton" affirmative>Send </paper-button>
|
||||
</template>
|
||||
14
rpg-docs/client/views/feedback/feedback.js
Normal file
14
rpg-docs/client/views/feedback/feedback.js
Normal file
@@ -0,0 +1,14 @@
|
||||
Template.feedback.events({
|
||||
"tap #sendButton": function(event, instance) {
|
||||
var report = {};
|
||||
report.title = instance.find("#feedbackTitle").value;
|
||||
report.severity = instance.find("#severity").value;
|
||||
report.type = instance.find(".typeMenu").selected;
|
||||
report.description = instance.find("#feedbackDescription").value;
|
||||
report.metaData = {
|
||||
url: window.location.href,
|
||||
session: _.pairs(Session.keys),
|
||||
};
|
||||
Meteor.call("insertReport", report);
|
||||
}
|
||||
});
|
||||
7
rpg-docs/client/views/guide/guide.css
Normal file
7
rpg-docs/client/views/guide/guide.css
Normal file
@@ -0,0 +1,7 @@
|
||||
.wallOfText h2{
|
||||
margin-top: 24px
|
||||
}
|
||||
|
||||
.wallOfText p{
|
||||
margin-top: 8px;
|
||||
}
|
||||
@@ -1,90 +1,92 @@
|
||||
<template name="guide">
|
||||
<h1>Dicecloud Beta</h1>
|
||||
<p>Welcome to the Dicecloud beta. Please don't share the link with people you don't actively play with, since the beta is intended to be small, and your experience will probably get laggy if it gets more traffic than I'm expecting.</p>
|
||||
<p>The beta is going to start with just the character sheet. You can play D&D without minis and maps, without a pre-written adventure, you can play without a lot of things, but the character sheet is necessary. So I'm starting here and working my way outwards.</p>
|
||||
<p>I will eventually have public bug tracking and feature requests going, but for now I'm going to track comments, feedback and suggestions on <a href="http://reddit.com/r/dicecloud">this subreddit</a>. If you've never used reddit before, all you need is a username and password to sign up. So it should be pretty accessible.</p>
|
||||
<h2>Character Sheet Philosophy</h2>
|
||||
<p>Setting up your character on Dicecloud is going to take you a little longer than just filling it in on a paper character sheet would have. The goal of using an online sheet is to make actually playing the game more streamlined, and ultimately more fun. So putting a little extra effort into setting up your character now will pay off over and over again once you're playing.</p>
|
||||
<p>The idea is to track where each number comes from, and allow you to easily make changes on the fly.</p>
|
||||
<p>Lets look at a hypothetical example.</p>
|
||||
<p>You need to swim through a sunken section of dungeon to fetch the quest's Thing.<br>You'll need to take off your magical Plate Armor of +1 Constitution to swim without sinking, of course. Taking it off will change your armor class, your speed and your constitution, which in turn changes your hitpoints and your constitution saving throw. Working out all those changes in the middle of a game will drag the game to a hault. <br> Fortunately you have a digital character sheet, so it's a matter of dragging your Plate Armor +1 Con from your "equipment" box to your "backpack" box and you're done. Your hitpoints change correctly, your saving throws are up to date, your armor class goes back to reflecting the fact that you have natural armor from being a dragonborn. Your character sheet keeps up and you ultimately get more time to play the game. Huzzah!</p>
|
||||
<h2>Creating a Character</h2>
|
||||
<ul>
|
||||
<li>In the <a href={{pathFor route="characterList"}}>character list</a>, click the plus button, floating in the bottom left corner.</li>
|
||||
<li>Give your character a name, gender and race, these can be changed later if you change your mind. Then click the Add button</li>
|
||||
<li>Your new character should open, with most of its attributes and abilities at zero.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Adding Racial Effects</h2>
|
||||
<p>You have already given your character a race, but you haven't yet specified what that race does for your character, so lets do that.</p>
|
||||
<ul>
|
||||
<li>Click the Journal tab</li>
|
||||
<li>In the card that displays your level, click on your race to open the racial dialog box</li>
|
||||
<li>Click the edit button in the top corner of the racial dialog</li>
|
||||
</ul>
|
||||
<p>In the edit mode of the racial dialog you can change your race's name and add effects and proficiencies your race gives you. We will only be adding the base traits our race gives us, specific features can go in the features tab so we can more easily reference them later.</p>
|
||||
<p>Lets add some of the effects all races will give.</p>
|
||||
<ul>
|
||||
<li>Click the Add Effect button, a new effect should appear</li>
|
||||
<li>In the Stat Group dropdown box, choose "Stats"</li>
|
||||
<li>The second dropdown lets us choose which stat to effect, choose "Speed"</li>
|
||||
<li>The third dropdown lets us choose how to effect that stat, choose "Base Value", since our character's base speed comes from their race</li>
|
||||
<li>Finally, input the value for our characters speed, it'll probably be 30 unless you chose a slower race, such as a dwarf</li>
|
||||
<li>Close the Race dialog and navigate to the Stats tab</li>
|
||||
<li>The speed card should now correctly display the character's speed</li>
|
||||
<li>Click the speed card to see how that value was calculated</li>
|
||||
<li>Currently there is only one number effecting the total, the speed from our race, but as more effects from different sources start impacting our character's speed, they will show up here.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Adding your ability scores</h2>
|
||||
<p>Your character currently doesn't have any ability scores, so lets fix that. Whether you roll your abilities or point-buy them, lets add a feature to represent where they came from</p>
|
||||
<ul>
|
||||
<li>Navigate to the <emd>Features</emd> tab</li>
|
||||
<li>Click the plus button in the bottom left to add a new feature</li>
|
||||
<li>Give the Feature a name, like <em>Point Buy</em></li>
|
||||
<li>Leave the feature as always enabled, don't limit its uses, and leave the description blank</li>
|
||||
<li>Click the <em>Add Effect</em> button</li>
|
||||
<li>For <em>Stat Group</em> choose <em>Ability Scores</em></li>
|
||||
<li>For <em>Stat</em> choose <em>Strength</em></li>
|
||||
<li>For the operation choose <em>Base Value</em></li>
|
||||
<li>Input your character's rolled or point-bought strength, without the racial modfier</li>
|
||||
<li>Repeat for the rest of your ability scores</li>
|
||||
</ul>
|
||||
<p>You can now check that your ability scores appear on your <em>Stats</em> page and that your skills that use them have their values calculated accordingly.</p>
|
||||
<p>We didn't include your character's racial ability modifiers in the feature, so you should go back to your character's racial dialog and add them in there as effects. Remember to use the add operation, rather than base value, since your race adds to your ability scores.</p>
|
||||
<div class="wallOfText">
|
||||
<h1>Dicecloud Beta</h1>
|
||||
<p>Welcome to the Dicecloud beta. Please don't share the link with people you don't actively play with, since the beta is intended to be small, and your experience will probably get laggy if it gets more traffic than I'm expecting.</p>
|
||||
<p>The beta is going to start with just the character sheet. You can play D&D without minis and maps, without a pre-written adventure, you can play without a lot of things, but the character sheet is necessary. So I'm starting here and working my way outwards.</p>
|
||||
<p>I will eventually have public bug tracking and feature requests going, but for now I'm going to track comments, feedback and suggestions on <a href="http://reddit.com/r/dicecloud">this subreddit</a>. If you've never used reddit before, all you need is a username and password to sign up. So it should be pretty accessible.</p>
|
||||
<h2>Character Sheet Philosophy</h2>
|
||||
<p>Setting up your character on Dicecloud is going to take you a little longer than just filling it in on a paper character sheet would have. The goal of using an online sheet is to make actually playing the game more streamlined, and ultimately more fun. So putting a little extra effort into setting up your character now will pay off over and over again once you're playing.</p>
|
||||
<p>The idea is to track where each number comes from, and allow you to easily make changes on the fly.</p>
|
||||
<p>Lets look at a hypothetical example.</p>
|
||||
<p>You need to swim through a sunken section of dungeon to fetch the quest's Thing.<br>You'll need to take off your magical Plate Armor of +1 Constitution to swim without sinking, of course. Taking it off will change your armor class, your speed and your constitution, which in turn changes your hitpoints and your constitution saving throw. Working out all those changes in the middle of a game will drag the game to a hault. <br> Fortunately you have a digital character sheet, so it's a matter of dragging your Plate Armor +1 Con from your "equipment" box to your "backpack" box and you're done. Your hitpoints change correctly, your saving throws are up to date, your armor class goes back to reflecting the fact that you have natural armor from being a dragonborn. Your character sheet keeps up and you ultimately get more time to play the game. Huzzah!</p>
|
||||
<h2>Creating a Character</h2>
|
||||
<ul>
|
||||
<li>In the <a href={{pathFor route="characterList"}}>character list</a>, click the plus button, floating in the bottom right corner.</li>
|
||||
<li>Give your character a name, gender and race, these can be changed later if you change your mind. Then click the Add button</li>
|
||||
<li>Your new character should open, with most of its attributes and abilities at zero.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Adding Racial Effects</h2>
|
||||
<p>You have already given your character a race, but you haven't yet specified what that race does for your character, so lets do that.</p>
|
||||
<ul>
|
||||
<li>Click the Journal tab</li>
|
||||
<li>In the card that displays your level, click on your race to open the racial dialog box</li>
|
||||
<li>Click the edit button in the top corner of the racial dialog</li>
|
||||
</ul>
|
||||
<p>In the edit mode of the racial dialog you can change your race's name and add effects and proficiencies your race gives you. We will only be adding the base traits our race gives us, specific features can go in the features tab so we can more easily reference them later.</p>
|
||||
<p>Lets add some of the effects all races will give.</p>
|
||||
<ul>
|
||||
<li>Click the Add Effect button, a new effect should appear</li>
|
||||
<li>In the Stat Group dropdown box, choose "Stats"</li>
|
||||
<li>The second dropdown lets us choose which stat to effect, choose "Speed"</li>
|
||||
<li>The third dropdown lets us choose how to effect that stat, choose "Base Value", since our character's base speed comes from their race</li>
|
||||
<li>Finally, input the value for our characters speed, it'll probably be 30 unless you chose a slower race, such as a dwarf</li>
|
||||
<li>Close the Race dialog and navigate to the Stats tab</li>
|
||||
<li>The speed card should now correctly display the character's speed</li>
|
||||
<li>Click the speed card to see how that value was calculated</li>
|
||||
<li>Currently there is only one number effecting the total, the speed from our race, but as more effects from different sources start impacting our character's speed, they will show up here.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Adding your ability scores</h2>
|
||||
<p>Your character currently doesn't have any ability scores, so lets fix that. Whether you roll your abilities or point-buy them, lets add a feature to represent where they came from</p>
|
||||
<ul>
|
||||
<li>Navigate to the <emd>Features</emd> tab</li>
|
||||
<li>Click the plus button in the bottom right to add a new feature</li>
|
||||
<li>Give the Feature a name, like <em>Point Buy</em></li>
|
||||
<li>Leave the feature as always enabled, don't limit its uses, and leave the description blank</li>
|
||||
<li>Click the <em>Add Effect</em> button</li>
|
||||
<li>For <em>Stat Group</em> choose <em>Ability Scores</em></li>
|
||||
<li>For <em>Stat</em> choose <em>Strength</em></li>
|
||||
<li>For the operation choose <em>Base Value</em></li>
|
||||
<li>Input your character's rolled or point-bought strength, without the racial modfier</li>
|
||||
<li>Repeat for the rest of your ability scores</li>
|
||||
</ul>
|
||||
<p>You can now check that your ability scores appear on your <em>Stats</em> page and that your skills that use them have their values calculated accordingly.</p>
|
||||
<p>We didn't include your character's racial ability modifiers in the feature, so you should go back to your character's racial dialog and add them in there as effects. Remember to use the add operation, rather than base value, since your race adds to your ability scores.</p>
|
||||
<p>By separating the source of your character's stats you can easily check how your character got their ability scores and stats, even after 20 levels, without getting confused or making mistakes.</p>
|
||||
|
||||
<h2>Adding a Class</h2>
|
||||
<p>Currently your character is at level 0, because they don't have any class levels. Let's fix that.</p>
|
||||
<ul>
|
||||
<li>Click the plus button in the card that currently says "Level 0"</li>
|
||||
<li>A new class has now been added, name the class in the Class Name input and leave the level as 1</li>
|
||||
</ul>
|
||||
<p>We now have a class, lets add the saving throw proficiencies it gives us.</p>
|
||||
<ul>
|
||||
<li>Click the Add Proficiency button</li>
|
||||
<li>Click the dropdown box that currently has "Skill" selected, and choose "Saving Throw" instead</li>
|
||||
<li>In the second dropdown choose the first saving throw your class gives you</li>
|
||||
<li>The third dropdown lets us specify if we have half or double our proficiency bonus for this proficiency, leave it at the default "proficient" for now</li>
|
||||
</ul>
|
||||
<p>If you navigate back to the stat page, you will see that you now have a proficiency bonus, based on your class level, and the saving throw you are proficienct in will take your proficiency bonus into account.</p>
|
||||
<p>One of the most important things your class gives you is your hitpoints, so lets go add those now.</p>
|
||||
<ul>
|
||||
<li>Navigate to the class dialog box by clicking on your class name in the journal tab and hitting the edit button</li>
|
||||
<li>Click the Add Effect button</li>
|
||||
<li>Choose the <em>Stats</em> stat group, and choose the <em>Hitpoints</em> stat</li>
|
||||
<li>Choose the <em>Base Value</em> operation</li>
|
||||
</ul>
|
||||
<p>Now we need to decide how many hitpoints our class gives us. We will assume that we take the constant hitpoints per level, since it's both the rule used for league play and it's statistically advantageous over rolling for hitpoints every level.</p>
|
||||
<p>We could work out our hit points every level and change the effect each time, but we can do one better, we can input the calculation directly into the value field and have the character sheet figure it out for us</p>
|
||||
<p>Let's assume we are rolling a fighter, so in the class name you typed in "Fighter" (with the capital F, but without the quote marks). A fighter gets 10 hp at first level and 6 hitpoints every level after that.</p>
|
||||
<p>Lets rather split that into 4 bonus hitpoints at first level, and 6 hitpoints for every fighter level your character has. We can the write this as <em>4 + 6*FighterLevel</em> where the * represents multiplication.</p>
|
||||
<p><em>Note, we don't add the constitution modifier here, that's already taken care of by default, since all characters add their constitution modifier to their hit points</em></p>
|
||||
<ul>
|
||||
<li>In the value field input <em>4 + 6 * FighterLevel</em>, the spaces aren't needed, but you must spell your class name exactly as it is spelt in the class name input box, capital letters and all, in our case "Fighter"</li>
|
||||
<li>Create a new effect that effects the base value of <em>d10 Hit Dice</em> with the value of <em>FighterLevel</em>, since we also get our fighters level worth of hit dice</li>
|
||||
<li>Check how your changes are reflected in the <em>Stats</em> tab</li>
|
||||
<li>Change your level and check that the <em>Stats</em> tab gets updated accordingly</li>
|
||||
</ul>
|
||||
<p>You can try all sorts of calculations in your effects and in certain other places too. For example if you had some feature that is used a number of times equal to your wisdom modifier or 1, whichever is lower, you could limit its uses to <em>min(1, wisdomMod)</em> and the character sheet will figure it out for you, and update itself if you wisdom modifier happens to change later.</p>
|
||||
|
||||
<h2>Adding a Class</h2>
|
||||
<p>Currently your character is at level 0, because they don't have any class levels. Let's fix that.</p>
|
||||
<ul>
|
||||
<li>Click the plus button in the card that currently says "Level 0"</li>
|
||||
<li>A new class has now been added, name the class in the Class Name input and leave the level as 1</li>
|
||||
</ul>
|
||||
<p>We now have a class, lets add the saving throw proficiencies it gives us.</p>
|
||||
<ul>
|
||||
<li>Click the Add Proficiency button</li>
|
||||
<li>Click the dropdown box that currently has "Skill" selected, and choose "Saving Throw" instead</li>
|
||||
<li>In the second dropdown choose the first saving throw your class gives you</li>
|
||||
<li>The third dropdown lets us specify if we have half or double our proficiency bonus for this proficiency, leave it at the default "proficient" for now</li>
|
||||
</ul>
|
||||
<p>If you navigate back to the stat page, you will see that you now have a proficiency bonus, based on your class level, and the saving throw you are proficienct in will take your proficiency bonus into account.</p>
|
||||
<p>One of the most important things your class gives you is your hitpoints, so lets go add those now.</p>
|
||||
<ul>
|
||||
<li>Navigate to the class dialog box by clicking on your class name in the journal tab and hitting the edit button</li>
|
||||
<li>Click the Add Effect button</li>
|
||||
<li>Choose the <em>Stats</em> stat group, and choose the <em>Hitpoints</em> stat</li>
|
||||
<li>Choose the <em>Base Value</em> operation</li>
|
||||
</ul>
|
||||
<p>Now we need to decide how many hitpoints our class gives us. We will assume that we take the constant hitpoints per level, since it's both the rule used for league play and it's statistically advantageous over rolling for hitpoints every level.</p>
|
||||
<p>We could work out our hit points every level and change the effect each time, but we can do one better, we can input the calculation directly into the value field and have the character sheet figure it out for us</p>
|
||||
<p>Let's assume we are rolling a fighter, so in the class name you typed in "Fighter" (with the capital F, but without the quote marks). A fighter gets 10 hp at first level and 6 hitpoints every level after that.</p>
|
||||
<p>Lets rather split that into 4 bonus hitpoints at first level, and 6 hitpoints for every fighter level your character has. We can the write this as <em>4 + 6*FighterLevel</em> where the * represents multiplication.</p>
|
||||
<p><em>Note, we don't add the constitution modifier here, that's already taken care of by default, since all characters add their constitution modifier to their hit points</em></p>
|
||||
<ul>
|
||||
<li>In the value field input <em>4 + 6 * FighterLevel</em>, the spaces aren't needed, but you must spell your class name exactly as it is spelt in the class name input box, capital letters and all, in our case "Fighter"</li>
|
||||
<li>Create a new effect that effects the base value of <em>d10 Hit Dice</em> with the value of <em>FighterLevel</em>, since we also get our fighters level worth of hit dice</li>
|
||||
<li>Check how your changes are reflected in the <em>Stats</em> tab</li>
|
||||
<li>Change your level and check that the <em>Stats</em> tab gets updated accordingly</li>
|
||||
</ul>
|
||||
<p>You can try all sorts of calculations in your effects and in certain other places too. For example if you had some feature that is used a number of times equal to your wisdom modifier or 1, whichever is lower, you could limit its uses to <em>min(1, wisdomMod)</em> and the character sheet will figure it out for you, and update itself if you wisdom modifier happens to change later.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,18 +1,20 @@
|
||||
<template name="layout">
|
||||
|
||||
<core-drawer-panel>
|
||||
<core-header-panel drawer navigation flex mode="seamed" class="white">
|
||||
<div id="accountSummary">
|
||||
{{> loginButtons}}
|
||||
{{#if currentUser}}
|
||||
<div id="profileLink" style="text-decoration: underline; cursor: pointer;">
|
||||
My account
|
||||
{{profileLink}}
|
||||
</div>
|
||||
{{else}}
|
||||
<a href="/sign-in" style="color: white;">Sign in</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="navPanel">
|
||||
<core-item id="homeNav" icon="home" label="Home"></core-item>
|
||||
{{> characterSideList}}
|
||||
<core-item id="feedback" icon="bug-report" label="Send Feedback"></core-item>
|
||||
<core-item id="changeLog" icon="list" label="Change Log"></core-item>
|
||||
</div>
|
||||
</core-header-panel>
|
||||
<core-animated-pages main
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
Template.layout.onCreated(function() {
|
||||
this.subscribe("user");
|
||||
});
|
||||
|
||||
Template.layout.rendered = function() {
|
||||
$(window).on("popstate", GlobalUI.popStateHandler);
|
||||
};
|
||||
@@ -9,7 +13,11 @@ Template.layout.destroyed = function() {
|
||||
Template.layout.helpers({
|
||||
notSelected: function(){
|
||||
return Session.get("global.ui.detailShow") ? "not-selected" : null;
|
||||
}
|
||||
},
|
||||
profileLink: function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username || user.username || "My Account";
|
||||
},
|
||||
});
|
||||
|
||||
Template.layout.events({
|
||||
@@ -19,4 +27,14 @@ Template.layout.events({
|
||||
"tap #profileLink": function(event, instance){
|
||||
Router.go("profile");
|
||||
},
|
||||
"tap #feedback": function(event, instance) {
|
||||
GlobalUI.showDialog({
|
||||
heading: "Feedback",
|
||||
template: "feedback",
|
||||
fullOnMobile: true,
|
||||
});
|
||||
},
|
||||
"tap #changeLog": function(event, instance) {
|
||||
Router.go("changeLog");
|
||||
},
|
||||
});
|
||||
|
||||
20
rpg-docs/client/views/meta/changeLog/changeLog.html
Normal file
20
rpg-docs/client/views/meta/changeLog/changeLog.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<template name="changeLog">
|
||||
<core-toolbar class="blue-grey white-text">
|
||||
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
|
||||
<div flex>
|
||||
Change Log
|
||||
</div>
|
||||
</core-toolbar>
|
||||
<div class="changeLog scroll-y" fit>
|
||||
{{#each changeLogs}}
|
||||
<paper-shadow class="white padded" style="margin: 8px;">
|
||||
<h2>{{version}}</h2>
|
||||
<ul>
|
||||
{{#each changes}}
|
||||
<li>{{this}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</paper-shadow>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
11
rpg-docs/client/views/notFound/notFound.html
Normal file
11
rpg-docs/client/views/notFound/notFound.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<template name="notFound">
|
||||
<div layout vertical center center-justified fit>
|
||||
<h2>The data for the page you requested could not be found.</h2>
|
||||
{{#if currentUser}}
|
||||
<h2>It might not exist, or you might not have permission to view it.</h2>
|
||||
{{else}}
|
||||
<h2>Perhaps you need to sign in first:</h2>
|
||||
{{atForm}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -20,12 +20,12 @@
|
||||
aria-label="Delete Feature"
|
||||
noink></paper-icon-button>
|
||||
{{else}}
|
||||
{{#unless hideEdit}}
|
||||
{{#if showEdit}}
|
||||
<paper-icon-button id="editButton"
|
||||
icon="create"
|
||||
aria-label="Delete Feature"
|
||||
noink></paper-icon-button>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</core-toolbar>
|
||||
<div class="detailContent">
|
||||
|
||||
@@ -11,6 +11,18 @@ Template.baseDialog.helpers({
|
||||
editing: function(){
|
||||
return Template.instance().editing.get();
|
||||
},
|
||||
showEdit: function() {
|
||||
if (this.hideEdit) return false;
|
||||
var charId = Template.parentData().charId;
|
||||
if (charId){
|
||||
var char = Characters.findOne(charId);
|
||||
var userId = Meteor.userId();
|
||||
if (char && userId)
|
||||
return char.owner === userId ||
|
||||
_.contains(char.writers, userId);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
Template.baseDialog.events({
|
||||
|
||||
@@ -3,11 +3,7 @@
|
||||
<core-toolbar class="blue-grey white-text">
|
||||
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
|
||||
<div id="username" class="clickable" flex>
|
||||
{{#if username}}
|
||||
{{username}}
|
||||
{{else}}
|
||||
Tap to set username
|
||||
{{/if}}
|
||||
{{profileName}}
|
||||
</div>
|
||||
</core-toolbar>
|
||||
<div id="userProfile" class="padded">
|
||||
@@ -20,5 +16,7 @@
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{> atForm}}
|
||||
{{> atNavButton }}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
Template.profile.helpers({
|
||||
profileName: function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username ||
|
||||
user.username ||
|
||||
"Tap to set username";
|
||||
}
|
||||
});
|
||||
|
||||
Template.profile.events({
|
||||
"tap #username": function(){
|
||||
if (this._id === Meteor.userId()){
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<template name="usernameDialog">
|
||||
{{#with currentUser}}
|
||||
<div>
|
||||
<paper-input id="usernameInput" label="Username" value={{username}}></paper-input>
|
||||
</div>
|
||||
{{/with}}
|
||||
<div>
|
||||
<paper-input id="usernameInput" label="Username" value={{profileName}}></paper-input>
|
||||
</div>
|
||||
<div style="color: red;" class="vertMargin">{{errorMessage}}</div>
|
||||
<paper-button id="cancelButton" affirmative> Cancel </paper-button>
|
||||
<paper-button id="changeButton" affirmative> Change Username </paper-button>
|
||||
<paper-button id="changeButton" disabled={{invalid}} affirmative> Change Username </paper-button>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,49 @@
|
||||
var getUsername = function() {
|
||||
var user = Meteor.user();
|
||||
return user.profile && user.profile.username || user.username;
|
||||
};
|
||||
|
||||
Template.usernameDialog.onCreated(function() {
|
||||
this.errorMessage = new ReactiveVar("");
|
||||
this.username = new ReactiveVar(getUsername());
|
||||
});
|
||||
|
||||
Template.usernameDialog.helpers({
|
||||
profileName: function() {
|
||||
return getUsername();
|
||||
},
|
||||
invalid: function() {
|
||||
return !!Template.instance().errorMessage.get();
|
||||
},
|
||||
errorMessage: function() {
|
||||
return Template.instance().errorMessage.get();
|
||||
},
|
||||
});
|
||||
|
||||
Template.usernameDialog.events({
|
||||
"change #usernameInput, input #usernameInput": function(event, instance) {
|
||||
var username = instance.find("#usernameInput").value;
|
||||
username = username.trim().toLowerCase().replace(/\s+/gm, "");
|
||||
if (username.length < 3){
|
||||
instance.errorMessage.set("Username too short");
|
||||
} else {
|
||||
instance.errorMessage.set("Validating...");
|
||||
Meteor.call("getUserId", username, function(err, userId){
|
||||
if (userId && userId !== Meteor.userId())
|
||||
instance.errorMessage.set("This username is taken");
|
||||
else
|
||||
instance.errorMessage.set("");
|
||||
});
|
||||
}
|
||||
},
|
||||
"tap #changeButton": function(event, instance){
|
||||
var username = instance.find("#usernameInput").value;
|
||||
username = username.trim().replace(/\s+/gm, " ");
|
||||
var profileName = username;
|
||||
username = username.toLowerCase().replace(/\s+/gm, "");
|
||||
Meteor.users.update(
|
||||
Meteor.userId(),
|
||||
{$set: {username: instance.find("#usernameInput").value}}
|
||||
{$set: {username: username, "profile.username": profileName}}
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
13
rpg-docs/client/views/user/titledAtForm/titledAtForm.html
Normal file
13
rpg-docs/client/views/user/titledAtForm/titledAtForm.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<template name="titledAtForm">
|
||||
<core-toolbar class="blue-grey white-text">
|
||||
<core-icon-button icon="menu" core-drawer-toggle></core-icon-button>
|
||||
<div flex>
|
||||
|
||||
</div>
|
||||
</core-toolbar>
|
||||
<div class="scroll-y padded" fit layout vertical center center-justified>
|
||||
<paper-shadow class="white" style="max-width: 400px;">
|
||||
{{> atForm}}
|
||||
</paper-shadow>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,3 +1,14 @@
|
||||
Meteor.methods({
|
||||
canWriteCharacter: function(charId) {
|
||||
var userId = this.userId;
|
||||
var char = Characters.findOne(
|
||||
charId,
|
||||
{fields: {owner: 1, writers: 1}}
|
||||
);
|
||||
return (userId && char.owner === userId || _.contains(char.writers, userId));
|
||||
},
|
||||
});
|
||||
|
||||
CHARACTER_SUBSCHEMA_ALLOW = {
|
||||
// the user must be logged in, and the user must be a writer of the character
|
||||
insert: function(userId, doc) {
|
||||
|
||||
71
rpg-docs/lib/constants/useraccountsConfig.js
Normal file
71
rpg-docs/lib/constants/useraccountsConfig.js
Normal file
@@ -0,0 +1,71 @@
|
||||
AccountsTemplates.configure({
|
||||
//behaviour
|
||||
confirmPassword: true,
|
||||
enablePasswordChange: true,
|
||||
enforceEmailVerification: true,
|
||||
overrideLoginErrors: false,
|
||||
sendVerificationEmail: true,
|
||||
lowercaseUsername: true,
|
||||
//appearance
|
||||
continuousValidation: true,
|
||||
negativeValidation: true,
|
||||
negativeFeedback: true,
|
||||
showValidating: true,
|
||||
showAddRemoveServices: true,
|
||||
showForgotPasswordLink: true,
|
||||
showResendVerificationEmailLink: true,
|
||||
});
|
||||
|
||||
AccountsTemplates.configureRoute("changePwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("enrollAccount", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("forgotPwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("resetPwd", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("signIn", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("signUp", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("verifyEmail", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
AccountsTemplates.configureRoute("resendVerificationEmail", {
|
||||
template: "titledAtForm",
|
||||
});
|
||||
|
||||
if (Meteor.isServer){
|
||||
Meteor.methods({
|
||||
"userExists": function(username){
|
||||
return !!Meteor.users.findOne({username: username});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
AccountsTemplates.addField({
|
||||
_id: "username",
|
||||
type: "text",
|
||||
required: true,
|
||||
func: function(value){
|
||||
if (Meteor.isClient) {
|
||||
var self = this;
|
||||
Meteor.call("userExists", value, function(err, userExists){
|
||||
if (!userExists)
|
||||
self.setSuccess();
|
||||
else
|
||||
self.setError("This username is taken");
|
||||
self.setValidating(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Server
|
||||
return Meteor.call("userExists", value);
|
||||
},
|
||||
});
|
||||
@@ -9,23 +9,27 @@ evaluate = function(charId, string){
|
||||
}
|
||||
//ability modifiers
|
||||
var abilityMods = [
|
||||
"STRENGTHMOD",
|
||||
"DEXTERITYMOD",
|
||||
"CONSTITUTIONMOD",
|
||||
"INTELLIGENCEMOD",
|
||||
"WISDOMMOD",
|
||||
"CHARISMAMOD",
|
||||
"strengthMod",
|
||||
"dexterityMod",
|
||||
"constitutionMod",
|
||||
"intelligenceMod",
|
||||
"wisdomMod",
|
||||
"charismaMod",
|
||||
];
|
||||
if (_.contains(abilityMods, sub.toUpperCase())){
|
||||
if (_.contains(abilityMods, sub)){
|
||||
var slice = sub.slice(0, -3);
|
||||
return char.abilityMod(slice);
|
||||
try {
|
||||
return char.abilityMod(slice);
|
||||
} catch (e){
|
||||
return sub;
|
||||
}
|
||||
}
|
||||
//class levels
|
||||
if (/\w+levels?\b/gi.test(sub)){
|
||||
//strip out "level"
|
||||
var className = sub.replace(/levels?\b/gi, "");
|
||||
var cls = Classes.findOne({charId: charId, name: className});
|
||||
return cls && cls.level;
|
||||
return cls && cls.level || sub;
|
||||
}
|
||||
//character level
|
||||
if (sub.toUpperCase() === "LEVEL"){
|
||||
|
||||
@@ -42,6 +42,12 @@ var inheritParentProperties = function(doc, collection){
|
||||
"Document's parent does not exist"
|
||||
);
|
||||
var handMeDowns = _.pick(parent, collection.inheritedKeys);
|
||||
if (
|
||||
_.contains(collection.inheritedKeys, "charId") &&
|
||||
doc.parent.collection === "Characters"
|
||||
){
|
||||
handMeDowns.charId = doc.parent.id;
|
||||
}
|
||||
if (_.isEmpty(handMeDowns)) return;
|
||||
collection.update(doc._id, {$set: handMeDowns});
|
||||
};
|
||||
@@ -76,14 +82,13 @@ makeChild = function(collection, inheritedKeys){
|
||||
}
|
||||
});
|
||||
|
||||
if (Meteor.isClient) {
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
if (modifier && modifier.$set && modifier.$set.parent){
|
||||
//when we change parents, inherit its properties
|
||||
inheritParentProperties(doc, collection);
|
||||
}
|
||||
});
|
||||
}
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
if (modifier && modifier.$set && modifier.$set["parent.id"]){
|
||||
//when we change parents, inherit its properties
|
||||
console.log("re-inheriting")
|
||||
inheritParentProperties(doc, collection);
|
||||
}
|
||||
});
|
||||
|
||||
collection.softRemoveNode = collection.softRemoveNode || function(id){
|
||||
collection.softRemove(id);
|
||||
@@ -102,14 +107,12 @@ makeParent = function(collection, donatedKeys){
|
||||
donatedKeys = joinWithDefaultKeys(donatedKeys);
|
||||
var collectionName = collection._collection.name;
|
||||
//after changing, push the changes to all children
|
||||
if (Meteor.isClient) {
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
modifier = limitModifierToKeys(modifier, donatedKeys);
|
||||
doc = _.pick(doc, ["_id", "charId"]);
|
||||
if (!modifier) return;
|
||||
Meteor.call("updateChildren", doc, modifier, true);
|
||||
});
|
||||
}
|
||||
collection.after.update(function(userId, doc, fieldNames, modifier, options) {
|
||||
modifier = limitModifierToKeys(modifier, donatedKeys);
|
||||
doc = _.pick(doc, ["_id", "charId"]);
|
||||
if (!modifier) return;
|
||||
Meteor.call("updateChildren", doc, modifier, true);
|
||||
});
|
||||
collection.softRemoveNode = function(id){
|
||||
Meteor.call("softRemoveNode", collectionName, id);
|
||||
};
|
||||
|
||||
27
rpg-docs/server/lib/configuration/accountsMeldConfig.js
Normal file
27
rpg-docs/server/lib/configuration/accountsMeldConfig.js
Normal file
@@ -0,0 +1,27 @@
|
||||
AccountsMeld.configure({
|
||||
meldDBCallback: function(sourceUserId, destinationUserId){
|
||||
// Here you can modify every collection you need for the document referencing
|
||||
// to sourceUserId to be modified in order to point to destinationUserId
|
||||
Characters.update(
|
||||
{owner: sourceUserId},
|
||||
{$set: {owner: destinationUserId}},
|
||||
{multi: true}
|
||||
);
|
||||
Characters.update(
|
||||
{writers: sourceUserId},
|
||||
{
|
||||
$pull: {writers: sourceUserId},
|
||||
$addToSet: {writers: destinationUserId},
|
||||
},
|
||||
{multi: true}
|
||||
);
|
||||
Characters.update(
|
||||
{readers: sourceUserId},
|
||||
{
|
||||
$pull: {readers: sourceUserId},
|
||||
$addToSet: {readers: destinationUserId},
|
||||
},
|
||||
{multi: true}
|
||||
);
|
||||
},
|
||||
});
|
||||
3
rpg-docs/server/publications/changeLog.js
Normal file
3
rpg-docs/server/publications/changeLog.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Meteor.publish("changeLog", function(){
|
||||
return ChangeLogs.find();
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
Meteor.publish("singleCharacter", function(characterId){
|
||||
userId = this.userId;
|
||||
if (!userId) return [];
|
||||
var char = Characters.findOne({
|
||||
_id: characterId,
|
||||
$or: [
|
||||
@@ -30,5 +31,7 @@ Meteor.publish("singleCharacter", function(characterId){
|
||||
{fields: {username: 1}}
|
||||
),
|
||||
];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
3
rpg-docs/server/publications/user.js
Normal file
3
rpg-docs/server/publications/user.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Meteor.publish("user", function(){
|
||||
return Meteor.users.find(this.userId, {fields: {roles: 1}});
|
||||
});
|
||||
Reference in New Issue
Block a user