Merge branch 'master' into feature-conditions
This commit is contained in:
3
rpg-docs/client/globalHelpers/characterPath.js
Normal file
3
rpg-docs/client/globalHelpers/characterPath.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Template.registerHelper("characterPath", function(char) {
|
||||
return `\/character\/${char._id}\/${char.urlName || "-"}`;
|
||||
});
|
||||
@@ -24,6 +24,10 @@ Template.registerHelper("evaluateString", function(charId, string) {
|
||||
return evaluateString(charId, string);
|
||||
});
|
||||
|
||||
Template.registerHelper("evaluateSpellString", function(charId, spellListId, string) {
|
||||
return evaluateSpellString(charId, spellListId, string);
|
||||
});
|
||||
|
||||
Template.registerHelper("evaluateShortString", function(charId, string) {
|
||||
if (_.isString(string)){
|
||||
return evaluateString(
|
||||
|
||||
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
195
rpg-docs/client/lib/improvedInitiativeJson.js
Normal file
@@ -0,0 +1,195 @@
|
||||
improvedInitiativeJson = function(charId, options){
|
||||
options = options || {
|
||||
features: true,
|
||||
attacks: true,
|
||||
description: true,
|
||||
};
|
||||
var char = Characters.findOne(charId);
|
||||
if (!char) return;
|
||||
var baseValue = function(attributeName){
|
||||
return Characters.calculate.attributeBase(charId, attributeName);
|
||||
};
|
||||
var skillMod = function(skillName){
|
||||
return Characters.calculate.skillMod(charId, skillName);
|
||||
};
|
||||
var damageMods = getDamageMods(charId);
|
||||
return JSON.stringify({
|
||||
"Id": char._id,
|
||||
"Name": char.name,
|
||||
"Source": "DiceCloud",
|
||||
"Type": char.race,
|
||||
"HP": {
|
||||
"Value": baseValue("hitPoints"),
|
||||
"Notes": getHitDiceString(charId) || "",
|
||||
},
|
||||
"AC": {
|
||||
"Value": baseValue("armor"),
|
||||
"Notes": getArmorString(charId) || "",
|
||||
},
|
||||
"InitiativeModifier": skillMod("initiative"),
|
||||
"Speed": ["" + baseValue("speed")],
|
||||
"Abilities": {
|
||||
"Str": baseValue("strength"),
|
||||
"Dex": baseValue("dexterity"),
|
||||
"Con": baseValue("constitution"),
|
||||
"Cha": baseValue("charisma"),
|
||||
"Int": baseValue("intelligence"),
|
||||
"Wis": baseValue("wisdom"),
|
||||
},
|
||||
"DamageVulnerabilities": damageMods.vulnerabilities,
|
||||
"DamageResistances": damageMods.resistances,
|
||||
"DamageImmunities": damageMods.immunities,
|
||||
"ConditionImmunities": [],
|
||||
"Saves": [
|
||||
{"Name": "Str", "Modifier": skillMod("strengthSave")},
|
||||
{"Name": "Dex", "Modifier": skillMod("dexteritySave")},
|
||||
{"Name": "Con", "Modifier": skillMod("constitutionSave")},
|
||||
{"Name": "Int", "Modifier": skillMod("intelligenceSave")},
|
||||
{"Name": "Wis", "Modifier": skillMod("wisdomSave")},
|
||||
{"Name": "Cha", "Modifier": skillMod("charismaSave")},
|
||||
],
|
||||
"Skills": getSkills(charId),
|
||||
"Senses": [
|
||||
"passive Perception " +
|
||||
Characters.calculate.passiveSkill(charId, "perception")
|
||||
],
|
||||
"Languages": getLanguages(charId),
|
||||
"Challenge": "",
|
||||
"Traits": options.features ? getTraits(charId) : [],
|
||||
"Actions": options.attacks ? getActions(charId) : [],
|
||||
"Reactions": [],
|
||||
"LegendaryActions": [],
|
||||
"Description": options.description ? char.description : "",
|
||||
"Player": Meteor.user().username,
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
var getHitDiceString = function(charId){
|
||||
var d6 = Characters.calculate.attributeBase(charId, "d6HitDice");
|
||||
var d8 = Characters.calculate.attributeBase(charId, "d8HitDice");
|
||||
var d10 = Characters.calculate.attributeBase(charId, "d10HitDice");
|
||||
var d12 = Characters.calculate.attributeBase(charId, "d12HitDice");
|
||||
var con = Characters.calculate.abilityMod(charId,"constitution");
|
||||
var string = "" +
|
||||
(d6 ? `${d6}d6 + ` : "") +
|
||||
(d8 ? `${d8}d8 + ` : "") +
|
||||
(d10 ? `${d10}d10 + ` : "") +
|
||||
(d12 ? `${d12}d12 + ` : "") +
|
||||
con;
|
||||
}
|
||||
|
||||
var getArmorString = function(charId){
|
||||
var bases = Effects.find({
|
||||
charId: charId,
|
||||
stat: "armor",
|
||||
operation: "base",
|
||||
enabled: true,
|
||||
}).map(e => ({
|
||||
ame: e.name,
|
||||
value: evaluateEffect(charId, e),
|
||||
}));
|
||||
var base = bases.length && _.max(bases, b => b.value).name || "";
|
||||
var effects = Effects.find({
|
||||
charId: charId,
|
||||
stat: "armor",
|
||||
operation: {$ne: "base"},
|
||||
enabled: true,
|
||||
}).map(e => e.name);
|
||||
var strings = base ? [base] : [];
|
||||
strings = strings.concat(effects);
|
||||
return strings.join(", ");
|
||||
}
|
||||
|
||||
var getDamageMods = function(charId){
|
||||
// jscs:disable maximumLineLength
|
||||
var multipliers = [
|
||||
{name: "Acid", value: Characters.calculate.attributeValue(charId, "acidMultiplier")},
|
||||
{name: "Bludgeoning", value: Characters.calculate.attributeValue(charId, "bludgeoningMultiplier")},
|
||||
{name: "Cold", value: Characters.calculate.attributeValue(charId, "coldMultiplier")},
|
||||
{name: "Fire", value: Characters.calculate.attributeValue(charId, "fireMultiplier")},
|
||||
{name: "Force", value: Characters.calculate.attributeValue(charId, "forceMultiplier")},
|
||||
{name: "Lightning", value: Characters.calculate.attributeValue(charId, "lightningMultiplier")},
|
||||
{name: "Necrotic", value: Characters.calculate.attributeValue(charId, "necroticMultiplier")},
|
||||
{name: "Piercing", value: Characters.calculate.attributeValue(charId, "piercingMultiplier")},
|
||||
{name: "Poison", value: Characters.calculate.attributeValue(charId, "poisonMultiplier")},
|
||||
{name: "Psychic", value: Characters.calculate.attributeValue(charId, "psychicMultiplier")},
|
||||
{name: "Radiant", value: Characters.calculate.attributeValue(charId, "radiantMultiplier")},
|
||||
{name: "Slashing", value: Characters.calculate.attributeValue(charId, "slashingMultiplier")},
|
||||
{name: "Thunder", value: Characters.calculate.attributeValue(charId, "thunderMultiplier")},
|
||||
];
|
||||
// jscs:enable maximumLineLength
|
||||
multipliers = _.groupBy(multipliers, "value");
|
||||
var names = o => o.name;
|
||||
return {
|
||||
"immunities": _.map(multipliers["0"], names),
|
||||
"resistances": _.map(multipliers["0.5"], names),
|
||||
"vulnerabilities": _.map(multipliers["2"], names),
|
||||
};
|
||||
}
|
||||
|
||||
var getSkills = function(charId){
|
||||
var allSkills = [
|
||||
{name: "acrobatics", attribute: "dexterity"},
|
||||
{name: "animalHandling", attribute: "wisdom"},
|
||||
{name: "arcana", attribute: "intelligence"},
|
||||
{name: "athletics", attribute: "strength"},
|
||||
{name: "deception", attribute: "charisma"},
|
||||
{name: "history", attribute: "intelligence"},
|
||||
{name: "insight", attribute: "wisdom"},
|
||||
{name: "intimidation", attribute: "charisma"},
|
||||
{name: "investigation", attribute: "intelligence"},
|
||||
{name: "medicine", attribute: "wisdom"},
|
||||
{name: "nature", attribute: "intelligence"},
|
||||
{name: "perception", attribute: "wisdom"},
|
||||
{name: "performance", attribute: "charisma"},
|
||||
{name: "persuasion", attribute: "charisma"},
|
||||
{name: "religion", attribute: "intelligence"},
|
||||
{name: "sleightOfHand", attribute: "dexterity"},
|
||||
{name: "stealth", attribute: "dexterity"},
|
||||
{name: "survival", attribute: "wisdom"},
|
||||
];
|
||||
var skills = [];
|
||||
_.each(allSkills, skill => {
|
||||
var value = Characters.calculate.skillMod(charId, skill.name);
|
||||
var mod = Characters.calculate.abilityMod(charId, skill.attribute);
|
||||
if (value !== mod){
|
||||
skills.push({Name: skill.name, Modifier: value});
|
||||
}
|
||||
});
|
||||
return skills;
|
||||
};
|
||||
|
||||
var getLanguages = function(charId){
|
||||
return Proficiencies.find({
|
||||
charId,
|
||||
enabled: true,
|
||||
type: "language",
|
||||
}).map(l => l.name);
|
||||
};
|
||||
|
||||
var getTraits = function(charId){
|
||||
return Features.find(
|
||||
{charId: charId},
|
||||
{sort: {color: 1, name: 1}}
|
||||
).map(f => ({
|
||||
Name: f.name,
|
||||
// evaluateShortString helper
|
||||
Content: evaluateString(
|
||||
charId, f.description && f.description.split(/^( *[-*_]){3,} *(?:\n+|$)/m)[0]
|
||||
) || "",
|
||||
Usage: "",
|
||||
}));
|
||||
}
|
||||
|
||||
var getActions = function(charId){
|
||||
return Attacks.find(
|
||||
{charId, enabled: true},
|
||||
{sort: {color: 1, name: 1}}
|
||||
).map(a => ({
|
||||
Name: a.name,
|
||||
Content: `+${evaluate(charId, a.attackBonus)} to hit, ` +
|
||||
`${evaluateString(charId, a.damage)} ${a.damageType} damage, ` +
|
||||
`${a.details}`,
|
||||
Usage: "",
|
||||
}));
|
||||
}
|
||||
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
28
rpg-docs/client/lib/requestAnimationFramePolyfill.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
|
||||
|
||||
// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
|
||||
|
||||
// MIT license
|
||||
var lastTime = 0;
|
||||
var vendors = ["ms", "moz", "webkit", "o"];
|
||||
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
|
||||
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"];
|
||||
window.cancelAnimationFrame = window[vendors[x] + "CancelAnimationFrame"] ||
|
||||
window[vendors[x] + "CancelRequestAnimationFrame"];
|
||||
}
|
||||
|
||||
if (!window.requestAnimationFrame)
|
||||
window.requestAnimationFrame = function(callback, element) {
|
||||
var currTime = new Date().getTime();
|
||||
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
||||
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
|
||||
timeToCall);
|
||||
lastTime = currTime + timeToCall;
|
||||
return id;
|
||||
};
|
||||
|
||||
if (!window.cancelAnimationFrame)
|
||||
window.cancelAnimationFrame = function(id) {
|
||||
clearTimeout(id);
|
||||
};
|
||||
@@ -24,6 +24,10 @@
|
||||
<iron-icon icon="settings" item-icon></iron-icon>
|
||||
Settings
|
||||
</paper-icon-item>
|
||||
<paper-icon-item id="characterExport">
|
||||
<iron-icon icon="content-copy" item-icon></iron-icon>
|
||||
Export to Improved Initiative
|
||||
</paper-icon-item>
|
||||
</paper-menu>
|
||||
</paper-menu-button>
|
||||
{{/if}}
|
||||
|
||||
@@ -203,4 +203,11 @@ Template.characterSheet.events({
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
"click #characterExport": function(event, instance){
|
||||
pushDialogStack({
|
||||
data: this,
|
||||
template: "exportDialog",
|
||||
element: event.currentTarget.parentElement.parentElement,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,26 +20,28 @@
|
||||
<div class="effectEdit layout horizontal flex">
|
||||
<dicecloud-selector class="statMenu flex" selected={{stat}} selectable="paper-item" style="height: 100%; overflow-y: auto;">
|
||||
{{#each statGroups}}
|
||||
<div style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
||||
<div class="statGroupTitle clickable" style="font-weight: bold; margin-top: 16px; padding-left: 8px;">
|
||||
{{this}}
|
||||
</div>
|
||||
{{#each stats}}
|
||||
<paper-item name={{stat}}>{{name}}</paper-item>
|
||||
{{/each}}
|
||||
<iron-collapse opened={{isGroupSelected this ../stat}}>
|
||||
{{#each stats}}
|
||||
<paper-item name={{stat}} class="clickable">{{name}}</paper-item>
|
||||
{{/each}}
|
||||
</iron-collapse>
|
||||
{{/each}}
|
||||
</dicecloud-selector>
|
||||
{{#if operations}}
|
||||
<dicecloud-selector class="operationMenu flex" selected={{operation}} style="height: 100%; overflow-y: auto;">
|
||||
{{#each operations}}
|
||||
<paper-item name={{operation}}>{{name}}</paper-item>
|
||||
<paper-item name={{operation}} class="clickable">{{name}}</paper-item>
|
||||
{{/each}}
|
||||
</dicecloud-selector>
|
||||
{{else}} {{#if showMultiplierOperations}}
|
||||
<dicecloud-selector class="multiplierMenu flex"
|
||||
selected={{value}}>
|
||||
<paper-item name="0.5">Resistance</paper-item>
|
||||
<paper-item name="2">Vulnerability</paper-item>
|
||||
<paper-item name="0">Immunity</paper-item>
|
||||
<paper-item name="0.5" class="clickable">Resistance</paper-item>
|
||||
<paper-item name="2" class="clickable">Vulnerability</paper-item>
|
||||
<paper-item name="0" class="clickable">Immunity</paper-item>
|
||||
</dicecloud-selector>
|
||||
{{else}}
|
||||
<div class="flex" style="height: 100%;"></div>
|
||||
|
||||
@@ -149,47 +149,71 @@ Template.effectEdit.helpers({
|
||||
effectValue: function(){
|
||||
return this.calculation || this.value;
|
||||
},
|
||||
isGroupSelected: function(groupName, statName){
|
||||
var stat = statsDict[statName]
|
||||
return stat && (stat.group === groupName);
|
||||
},
|
||||
});
|
||||
|
||||
var setStat = function(statName, effectId){
|
||||
var setter = {stat: statName};
|
||||
var effect = Effects.findOne(this._id);
|
||||
var group = statsDict[statName].group;
|
||||
if (group === "Saving Throws" || group === "Skills"){
|
||||
// Skills must have a valid skill operation
|
||||
if (!_.contains(
|
||||
_.map(skillOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "add";
|
||||
}
|
||||
} else if (group !== "Weakness/Resistance"){
|
||||
// Attributes must have a valid attribute operation
|
||||
if (!_.contains(
|
||||
_.map(attributeOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "base";
|
||||
}
|
||||
} else {
|
||||
// Weakness/Resistance must have a mul operation and value
|
||||
if (effect.operation !== "mul"){
|
||||
setter.operation = "mul";
|
||||
}
|
||||
if (!_.contains([0, 0.5, 2], effect.value)){
|
||||
setter.value = 0.5;
|
||||
}
|
||||
}
|
||||
Effects.update(effectId, {$set: setter});
|
||||
};
|
||||
|
||||
var scrollAnimationId;
|
||||
var scrollElementToView = element => {
|
||||
var scrollFunction = function(){
|
||||
element.scrollIntoView();
|
||||
scrollAnimationId = requestAnimationFrame(scrollFunction);
|
||||
};
|
||||
return scrollFunction;
|
||||
}
|
||||
|
||||
Template.effectEdit.events({
|
||||
"click #deleteButton": function(event, instance){
|
||||
Effects.softRemoveNode(instance.data.id);
|
||||
GlobalUI.deletedToast(instance.data.id, "Effects", "Effect");
|
||||
popDialogStack();
|
||||
},
|
||||
"click .statGroupTitle": function(event, instance){
|
||||
var groupName = this.toString();
|
||||
var firstStat = statGroups[groupName][0].stat;
|
||||
setStat(firstStat, instance.data.id);
|
||||
scrollAnimationId = requestAnimationFrame(scrollElementToView(event.target));
|
||||
_.delay(() => cancelAnimationFrame(scrollAnimationId), 300);
|
||||
},
|
||||
"iron-select .statMenu": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
var statName = detail.item.getAttribute("name");
|
||||
if (statName == this.stat) return;
|
||||
var setter = {stat: statName};
|
||||
var group = Blaze.getData(detail.item).group;
|
||||
var effect = Effects.findOne(this._id);
|
||||
if (group === "Saving Throws" || group === "Skills"){
|
||||
// Skills must have a valid skill operation
|
||||
if (!_.contains(
|
||||
_.map(skillOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "add";
|
||||
}
|
||||
} else if (group !== "Weakness/Resistance"){
|
||||
// Attributes must have a valid attribute operation
|
||||
if (!_.contains(
|
||||
_.map(attributeOperations, ao => ao.operation),
|
||||
effect.operation
|
||||
)){
|
||||
setter.operation = "base";
|
||||
}
|
||||
} else {
|
||||
// Weakness/Resistance must have a mul operation and value
|
||||
if (effect.operation !== "mul"){
|
||||
setter.operation = "mul";
|
||||
}
|
||||
if (!_.contains([0, 0.5, 2], effect.value)){
|
||||
setter.value = 0.5;
|
||||
}
|
||||
}
|
||||
Effects.update(this._id, {$set: setter});
|
||||
setStat(statName, this._id);
|
||||
},
|
||||
"iron-select .operationMenu": function(event){
|
||||
var detail = event.originalEvent.detail;
|
||||
|
||||
@@ -33,7 +33,7 @@ Template.effectsEditList.events({
|
||||
template: "effectEdit",
|
||||
data: {id: effectId},
|
||||
element: event.currentTarget,
|
||||
returnElement: instance.find(`tr.effect[data-id='${effectId}']`),
|
||||
returnElement: () => instance.find(`tr.effect[data-id='${effectId}']`),
|
||||
});
|
||||
},
|
||||
"tap .edit-effect": function(event, template){
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.exportDialog .iiexport {
|
||||
overflow-y: auto;
|
||||
width: 100% !important;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<template name="exportDialog">
|
||||
<div class="exportDialog fit layout vertical">
|
||||
{{#with character}}
|
||||
<app-header fixed effects="waterfall">
|
||||
<app-toolbar>
|
||||
<div main-title>Export Character to Improved Initiative</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="form flex layout vertical">
|
||||
<paper-toggle-button id="exportFeatures" checked={{settings.exportFeatures}}>
|
||||
Features
|
||||
</paper-toggle-button>
|
||||
<paper-toggle-button id="exportAttacks" checked={{settings.exportAttacks}}>
|
||||
Attacks
|
||||
</paper-toggle-button>
|
||||
<paper-toggle-button id="exportDescription" checked={{settings.exportDescription}}>
|
||||
Description
|
||||
</paper-toggle-button>
|
||||
<div class="paper-font-title padded">JSON</div>
|
||||
<textarea class="flex iiexport">{{improvedInitiativeJson}}</textarea>
|
||||
<paper-button id="copyExportButton" class="red-button" raised>
|
||||
<iron-icon icon="content-copy"></iron-icon>
|
||||
Copy to Clipboard
|
||||
</paper-button>
|
||||
</div>
|
||||
<div class="buttons layout horizontal end-justified">
|
||||
<paper-button class="doneButton"> Done </paper-button>
|
||||
</div>
|
||||
{{/with}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,60 @@
|
||||
Template.exportDialog.helpers({
|
||||
character: function(){
|
||||
return Characters.findOne(this._id);
|
||||
},
|
||||
improvedInitiativeJson: function(){
|
||||
var options = {
|
||||
features: this.settings.exportFeatures,
|
||||
attacks: this.settings.exportAttacks,
|
||||
description: this.settings.exportDescription,
|
||||
}
|
||||
return improvedInitiativeJson(this._id, options);
|
||||
},
|
||||
});
|
||||
|
||||
Template.exportDialog.events({
|
||||
"change #exportFeatures": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportFeatures": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"change #exportAttacks": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportAttacks": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"change #exportDescription": function(event, template){
|
||||
Characters.update(this._id, {$set: {
|
||||
"settings.exportDescription": event.target.checked,
|
||||
}});
|
||||
},
|
||||
"click #copyExportButton": function(event, template){
|
||||
var copyTextarea = template.find(".iiexport");
|
||||
copyTextarea.select();
|
||||
var msg;
|
||||
try {
|
||||
var successful = document.execCommand("copy");
|
||||
var msg = successful ? "JSON copied to clipboard" : "Unable to copy JSON";
|
||||
} catch (err) {
|
||||
msg = "Unable to copy JSON";
|
||||
} finally {
|
||||
clearSelection();
|
||||
GlobalUI.toast(msg);
|
||||
}
|
||||
},
|
||||
"click .doneButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
|
||||
var clearSelection = function(){
|
||||
if (window.getSelection) {
|
||||
if (window.getSelection().empty) { // Chrome
|
||||
window.getSelection().empty();
|
||||
} else if (window.getSelection().removeAllRanges) { // Firefox
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
} else if (document.selection) { // IE?
|
||||
document.selection.empty();
|
||||
}
|
||||
};
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div>{{#markdown}}{{evaluateString charId description}}{{/markdown}}</div>
|
||||
<div>{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}</div>
|
||||
{{> attacksViewList charId=charId parentId=_id}}
|
||||
</template>
|
||||
|
||||
|
||||
@@ -61,6 +61,18 @@
|
||||
<td>Total</td>
|
||||
<td>{{characterCalculate "skillMod" charId skillName}}</td>
|
||||
</tr>
|
||||
{{#each passiveEffects}}
|
||||
<tr>
|
||||
<td>{{sourceName}}</td>
|
||||
<td>Passive Bonus: {{statValue}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{#if showPassiveTotal}}
|
||||
<tr class="paper-font-body2">
|
||||
<td>Passive Score</td>
|
||||
<td>{{characterCalculate "passiveSkill" charId skillName}}</td>
|
||||
</tr>
|
||||
{{/if}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ Template.skillDialogView.helpers({
|
||||
var char = Characters.findOne(this.charId);
|
||||
if (!char) return;
|
||||
var prof = Characters.calculate.proficiency(this.charId, this.skillName);
|
||||
var proficiencyBonus =
|
||||
var proficiencyBonus =
|
||||
Characters.calculate.attributeValue(this.charId, "proficiencyBonus");
|
||||
return prof * proficiencyBonus;
|
||||
},
|
||||
@@ -189,6 +189,23 @@ Template.skillDialogView.helpers({
|
||||
enabled: true,
|
||||
});
|
||||
},
|
||||
passiveEffects: function(){
|
||||
return Effects.find({
|
||||
charId: this.charId,
|
||||
stat: this.skillName,
|
||||
operation: "passiveAdd",
|
||||
enabled: true,
|
||||
});
|
||||
},
|
||||
showPassiveTotal: function(){
|
||||
if (this.skillName === "perception") return true;
|
||||
return Effects.find({
|
||||
charId: this.charId,
|
||||
stat: this.skillName,
|
||||
operation: "passiveAdd",
|
||||
enabled: true,
|
||||
}).count();
|
||||
},
|
||||
ability: function(){
|
||||
var opts = {fields: {}};
|
||||
opts.fields[this.skillName] = 1;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
{{#if conditionalCount}}
|
||||
*
|
||||
{{/if}}
|
||||
{{#if showPassive}}
|
||||
{{#if isPassiveShown}}
|
||||
({{characterCalculate "passiveSkill" ../_id skill}})
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
@@ -38,4 +38,16 @@ Template.skillRow.helpers({
|
||||
operation: "conditional",
|
||||
}).count();
|
||||
},
|
||||
isPassiveShown: function(){
|
||||
if (this.showPassive === "forced") return true;
|
||||
if (this.showPassive === "ifNeeded"){
|
||||
var charId = Template.parentData()._id;
|
||||
return Effects.find({
|
||||
charId,
|
||||
stat: this.skill,
|
||||
operation: "passiveAdd",
|
||||
enabled: true,
|
||||
}).count();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -51,24 +51,24 @@
|
||||
Skills
|
||||
</div>
|
||||
<div flex class="bottom list">
|
||||
{{> skillRow name="Acrobatics" skill="acrobatics"}}
|
||||
{{> skillRow name="Animal Handling" skill="animalHandling"}}
|
||||
{{> skillRow name="Arcana" skill="arcana"}}
|
||||
{{> skillRow name="Athletics" skill="athletics"}}
|
||||
{{> skillRow name="Deception" skill="deception"}}
|
||||
{{> skillRow name="History" skill="history"}}
|
||||
{{> skillRow name="Insight" skill="insight"}}
|
||||
{{> skillRow name="Intimidation" skill="intimidation"}}
|
||||
{{> skillRow name="Investigation" skill="investigation"}}
|
||||
{{> skillRow name="Medicine" skill="medicine"}}
|
||||
{{> skillRow name="Nature" skill="nature"}}
|
||||
{{> skillRow name="Perception" skill="perception" showPassive="true"}}
|
||||
{{> skillRow name="Performance" skill="performance"}}
|
||||
{{> skillRow name="Persuasion" skill="persuasion"}}
|
||||
{{> skillRow name="Religion" skill="religion"}}
|
||||
{{> skillRow name="Sleight of Hand" skill="sleightOfHand"}}
|
||||
{{> skillRow name="Stealth" skill="stealth"}}
|
||||
{{> skillRow name="Survival" skill="survival"}}
|
||||
{{> skillRow name="Acrobatics" skill="acrobatics" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Animal Handling" skill="animalHandling" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Arcana" skill="arcana" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Athletics" skill="athletics" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Deception" skill="deception" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="History" skill="history" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Insight" skill="insight" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Intimidation" skill="intimidation" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Investigation" skill="investigation" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Medicine" skill="medicine" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Nature" skill="nature" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Perception" skill="perception" showPassive="forced"}}
|
||||
{{> skillRow name="Performance" skill="performance" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Persuasion" skill="persuasion" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Religion" skill="religion" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Sleight of Hand" skill="sleightOfHand" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Stealth" skill="stealth" showPassive="ifNeeded"}}
|
||||
{{> skillRow name="Survival" skill="survival" showPassive="ifNeeded"}}
|
||||
</div>
|
||||
</paper-material>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,16 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.character-card .image {
|
||||
.partyHeader {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.partyHeader iron-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.partyHeader:hover iron-icon{
|
||||
visibility: initial;
|
||||
}
|
||||
|
||||
.character-card .initials {
|
||||
|
||||
@@ -10,31 +10,27 @@
|
||||
{{#if currentUser}}
|
||||
{{#if characters.count}}
|
||||
<div class="character-list layout horizontal wrap">
|
||||
{{# each characters}}
|
||||
<a class="character-card flex layout vertical end-justified" href="/character/{{_id}}">
|
||||
<iron-image class="fit {{colorClass}}"
|
||||
sizing="cover" preload fade src={{picture}}>
|
||||
</iron-image>
|
||||
{{#unless picture}}
|
||||
<div class="fit initials layout vertical center center-justified">
|
||||
{{initials name}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
<paper-item>
|
||||
<paper-item-body two-lines>
|
||||
<div class="name white87">
|
||||
{{name}}
|
||||
</div>
|
||||
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
|
||||
{{alignment}} {{gender}} {{race}}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
<paper-ripple></paper-ripple>
|
||||
</a>
|
||||
{{# each charactersWithNoParty}}
|
||||
{{> characterCard}}
|
||||
{{/each}}
|
||||
{{> gridPadding class="character-card flex layout vertical" num=12}}
|
||||
</div>
|
||||
{{# each party in parties}}
|
||||
<div class="party" data-id={{party._id}}>
|
||||
{{#with party}}
|
||||
<div class="partyHeader clickable paper-font-title padded">
|
||||
{{name}}
|
||||
<iron-icon icon="create"></iron-icon>
|
||||
</div>
|
||||
{{/with}}
|
||||
<div class="character-list layout horizontal wrap">
|
||||
{{# each charactersInParty party._id}}
|
||||
{{> characterCard}}
|
||||
{{/each}}
|
||||
{{> gridPadding class="character-card flex layout vertical" num=12}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<div layout vertical center center-justified class="padded">
|
||||
<div>You don't seem to have any characters yet</div>
|
||||
@@ -47,9 +43,46 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="fab-buffer"></div>
|
||||
<paper-fab class="floatyButton addCharacter"
|
||||
icon="add"
|
||||
title="Add"></paper-fab>
|
||||
{{#fabMenu}}
|
||||
<div>
|
||||
<paper-fab icon="social:group"
|
||||
class="addParty"
|
||||
mini>
|
||||
</paper-fab>
|
||||
<paper-tooltip position="left"> New Party </paper-tooltip>
|
||||
</div>
|
||||
<div>
|
||||
<paper-fab icon="face"
|
||||
class="addCharacter"
|
||||
mini>
|
||||
</paper-fab>
|
||||
<paper-tooltip position="left"> New Character </paper-tooltip>
|
||||
</div>
|
||||
{{/fabMenu}}
|
||||
</div>
|
||||
</app-header-layout>
|
||||
</template>
|
||||
|
||||
<template name="characterCard">
|
||||
<a class="character-card flex layout vertical end-justified" href="{{characterPath this}}">
|
||||
<iron-image class="fit {{colorClass}}"
|
||||
sizing="cover" preload fade src={{picture}}>
|
||||
</iron-image>
|
||||
{{#unless picture}}
|
||||
<div class="fit initials layout vertical center center-justified">
|
||||
{{initials name}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
<paper-item>
|
||||
<paper-item-body two-lines>
|
||||
<div class="name white87">
|
||||
{{name}}
|
||||
</div>
|
||||
<div secondary style="color: #8a8a8a; color: rgba(255,255,255,0.87);">
|
||||
{{alignment}} {{gender}} {{race}}
|
||||
</div>
|
||||
</paper-item-body>
|
||||
</paper-item>
|
||||
<paper-ripple></paper-ripple>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@@ -1,27 +1,57 @@
|
||||
Template.characterList.helpers({
|
||||
characters(){
|
||||
characters() {
|
||||
var userId = Meteor.userId();
|
||||
return Characters.find(
|
||||
{
|
||||
$or: [
|
||||
{readers: userId},
|
||||
{writers: userId},
|
||||
{owner: userId},
|
||||
]
|
||||
},
|
||||
{
|
||||
fields: {name: 1, picture: 1, color: 1, race: 1, alignment: 1, gender: 1},
|
||||
sort: {name: 1},
|
||||
}
|
||||
{$or: [{readers: userId}, {writers: userId}, {owner: userId}]},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
parties() {
|
||||
return Parties.find({owner: Meteor.userId()});
|
||||
},
|
||||
charactersInParty(partyId) {
|
||||
var userId = Meteor.userId();
|
||||
var party = Parties.findOne(partyId);
|
||||
return Characters.find(
|
||||
{
|
||||
_id: {$in: party.characters},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
charactersWithNoParty() {
|
||||
var userId = Meteor.userId();
|
||||
var charArrays = Parties.find({owner: userId}).map(p => p.characters);
|
||||
var partyChars = _.uniq(_.flatten(charArrays));
|
||||
return Characters.find(
|
||||
{
|
||||
_id: {$nin: partyChars},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Template.characterCard.helpers({
|
||||
initials(name){
|
||||
return name.replace(/[^A-Z]/g, "");
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Template.characterList.events({
|
||||
"tap .addCharacter": function(event, template) {
|
||||
"click .partyHeader": function(event, instance){
|
||||
pushDialogStack({
|
||||
template: "partyDialog",
|
||||
data: {
|
||||
_id: this._id,
|
||||
startEditing: true,
|
||||
},
|
||||
element: event.currentTarget.parentElement,
|
||||
});
|
||||
},
|
||||
"click .addCharacter": function(event, instance) {
|
||||
pushDialogStack({
|
||||
template: "newCharacterDialog",
|
||||
element: event.currentTarget,
|
||||
@@ -29,8 +59,23 @@ Template.characterList.events({
|
||||
if (!character) return;
|
||||
character.owner = Meteor.userId();
|
||||
let _id = Characters.insert(character);
|
||||
Router.go("characterSheet", {_id});
|
||||
let urlName = getSlug(character.name, {maintainCase: true}) || "-"
|
||||
Router.go("characterSheet", {_id, urlName});
|
||||
},
|
||||
})
|
||||
},
|
||||
"click .addParty": function(event, instance) {
|
||||
var partyId = Parties.insert({
|
||||
owner: Meteor.userId(),
|
||||
});
|
||||
pushDialogStack({
|
||||
template: "partyDialog",
|
||||
data: {
|
||||
_id: partyId,
|
||||
startEditing: true,
|
||||
},
|
||||
element: event.currentTarget,
|
||||
returnElement: instance.find(`.party[data-id='${partyId}']`),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,8 +2,21 @@
|
||||
prevent character names from wrapping
|
||||
*/
|
||||
|
||||
.character-name {
|
||||
.side-list .character-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.side-list .partyHead {
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.side-list .partyHead iron-icon {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.side-list .partyHead iron-icon.open {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,31 @@
|
||||
<template name="characterSideList">
|
||||
{{#if characters.count}}
|
||||
<div class="side-list">
|
||||
{{#each characters}}
|
||||
<a href={{pathFor route="characterSheet" data=this}} tabindex="-1" class="side-list-character characterRepresentative">
|
||||
<paper-item class="short">
|
||||
<div class="character-name">
|
||||
{{name}}
|
||||
</div>
|
||||
</paper-item>
|
||||
</a>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="side-list">
|
||||
{{#each charactersWithNoParty}}
|
||||
<a href={{characterPath this}} tabindex="-1" class="side-list-character characterRepresentative">
|
||||
<paper-item class="short">
|
||||
<div class="character-name">
|
||||
{{name}}
|
||||
</div>
|
||||
</paper-item>
|
||||
</a>
|
||||
{{/each}}
|
||||
{{#each parties}}
|
||||
<div class="paper-font-subhead partyHead">
|
||||
<iron-icon icon="chevron-right" class="{{#if isOpen _id}}open{{/if}}">
|
||||
</iron-icon>
|
||||
{{name}}
|
||||
</div>
|
||||
<iron-collapse opened={{isOpen _id}}>
|
||||
{{#each charactersInParty}}
|
||||
<a href={{characterPath this}} tabindex="-1" class="side-list-character characterRepresentative">
|
||||
<paper-item class="short">
|
||||
<div class="character-name">
|
||||
{{name}}
|
||||
</div>
|
||||
</paper-item>
|
||||
</a>
|
||||
{{/each}}
|
||||
</iron-collapse>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,33 +1,50 @@
|
||||
Template.characterSideList.onCreated(function() {
|
||||
this.subscribe("characterList");
|
||||
this.openedParties = new ReactiveVar(new Set());
|
||||
});
|
||||
|
||||
Template.characterSideList.helpers({
|
||||
characters: function() {
|
||||
parties() {
|
||||
var userId = Meteor.userId();
|
||||
return Parties.find({owner: userId});
|
||||
},
|
||||
charactersInParty() {
|
||||
var userId = Meteor.userId();
|
||||
return Characters.find(
|
||||
{
|
||||
$or: [
|
||||
{readers: userId},
|
||||
{writers: userId},
|
||||
{owner: userId},
|
||||
]
|
||||
_id: {$in: this.characters},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{
|
||||
fields: {name: 1},
|
||||
sort: {name: 1},
|
||||
}
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
charactersWithNoParty() {
|
||||
var userId = Meteor.userId();
|
||||
var charArrays = Parties.find({owner: userId}).map(p => p.characters);
|
||||
var partyChars = _.uniq(_.flatten(charArrays));
|
||||
return Characters.find(
|
||||
{
|
||||
_id: {$nin: partyChars},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
isOpen(id) {
|
||||
var openedParties = Template.instance().openedParties.get();
|
||||
console.log(openedParties);
|
||||
return openedParties.has(id);
|
||||
},
|
||||
});
|
||||
|
||||
Template.characterSideList.events({
|
||||
"tap .singleLineItem": function(event, instance) {
|
||||
//Router.go("characterSheet", {_id: this._id});
|
||||
$("core-drawer-panel").get(0).closeDrawer();
|
||||
},
|
||||
"tap core-item": function() {
|
||||
Router.go("characterList");
|
||||
$("core-drawer-panel").get(0).closeDrawer();
|
||||
"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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.partyEdit .inPartyCheckbox {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<template name="partyDialog">
|
||||
{{#with party}}
|
||||
{{#baseDialog title=name hideColor=true startEditing=true}}
|
||||
{{> partyDetails}}
|
||||
{{else}}
|
||||
{{> partyEdit}}
|
||||
{{/baseDialog}}
|
||||
{{/with}}
|
||||
</template>
|
||||
|
||||
<template name="partyDetails">
|
||||
<div class="fit layout vertical partyDetails" style="padding: 24px;">
|
||||
<div>
|
||||
{{#each character in getCharacters}}
|
||||
<div>{{character.name}}</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="partyEdit">
|
||||
<div class="layout vertical partyEdit" style="padding: 24px;">
|
||||
<paper-input class="partyNameInput" value={{name}} label="Party name">
|
||||
</paper-input>
|
||||
{{#each allCharacters}}
|
||||
<paper-checkbox checked={{charInParty _id}}
|
||||
class="inPartyCheckbox">
|
||||
{{name}}
|
||||
</paper-checkbox>
|
||||
{{/each}}
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,62 @@
|
||||
Template.partyDialog.helpers({
|
||||
party(){
|
||||
return Parties.findOne(this._id);
|
||||
}
|
||||
});
|
||||
|
||||
Template.partyDetails.helpers({
|
||||
getCharacters (){
|
||||
var userId = Meteor.userId();
|
||||
return Characters.find(
|
||||
{
|
||||
_id: {$in: this.characters},
|
||||
$or: [{readers: userId}, {writers: userId}, {owner: userId}],
|
||||
},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Template.partyEdit.helpers({
|
||||
allCharacters() {
|
||||
var userId = Meteor.userId();
|
||||
return Characters.find(
|
||||
{$or: [{readers: userId}, {writers: userId}, {owner: userId}]},
|
||||
{sort: {name: 1}}
|
||||
);
|
||||
},
|
||||
charInParty(charId) {
|
||||
return _.contains(Template.parentData().characters, charId);
|
||||
},
|
||||
});
|
||||
|
||||
Template.partyDialog.events({
|
||||
"click #deleteButton": function(event, instance){
|
||||
Parties.remove(instance.data._id);
|
||||
popDialogStack();
|
||||
},
|
||||
"click #doneEditingButton": function(event, instance){
|
||||
popDialogStack();
|
||||
},
|
||||
});
|
||||
|
||||
Template.partyEdit.events({
|
||||
"change .inPartyCheckbox": function(event, instance){
|
||||
var currentCharacters = this.characters;
|
||||
var checked = event.currentTarget.checked;
|
||||
var charId = this._id;
|
||||
var partyId = instance.data._id;
|
||||
if (checked){
|
||||
Parties.update(partyId, {$addToSet: {characters: charId}});
|
||||
} else {
|
||||
Parties.update(partyId, {$pull: {characters: charId}});
|
||||
}
|
||||
},
|
||||
"input .partyNameInput": function(event, instance){
|
||||
var name = event.currentTarget.value;
|
||||
Parties.update(this._id, {$set: {name}}, {
|
||||
removeEmptyStrings: false,
|
||||
trimStrings: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -4,11 +4,13 @@ Template.baseDialog.onCreated(function(){
|
||||
|
||||
Template.baseDialog.helpers({
|
||||
editing: function(){
|
||||
if (!Template.parentData() || !Template.parentData().charId) return true;
|
||||
return Template.instance().editing.get() &&
|
||||
canEditCharacter(Template.parentData().charId);
|
||||
},
|
||||
showEdit: function() {
|
||||
if (this.hideEdit) return false;
|
||||
if (!Template.parentData() || !Template.parentData().charId) return true;
|
||||
return canEditCharacter(Template.parentData().charId);
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user