diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js index 20e717e12..0b807d34f 100644 --- a/rpg-docs/Model/Character/Characters.js +++ b/rpg-docs/Model/Character/Characters.js @@ -184,6 +184,9 @@ Schemas.Character = new SimpleSchema({ defaultValue: "whitelist", allowedValues: ["whitelist", "public"], }, + "settings.exportFeatures": {type: Boolean, defaultValue: true}, + "settings.exportAttacks": {type: Boolean, defaultValue: true}, + "settings.exportDescription": {type: Boolean, defaultValue: true}, }); Characters.attachSchema(Schemas.Character); diff --git a/rpg-docs/client/views/character/characterSheet.html b/rpg-docs/client/views/character/characterSheet.html index bb9ce8250..cc6f11e36 100644 --- a/rpg-docs/client/views/character/characterSheet.html +++ b/rpg-docs/client/views/character/characterSheet.html @@ -24,6 +24,10 @@ Settings + + + Export to Improved Initiative + {{/if}} diff --git a/rpg-docs/client/views/character/characterSheet.js b/rpg-docs/client/views/character/characterSheet.js index 006c42f46..c7052c44f 100644 --- a/rpg-docs/client/views/character/characterSheet.js +++ b/rpg-docs/client/views/character/characterSheet.js @@ -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, + }); + }, }); diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.html b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html new file mode 100644 index 000000000..4f575ed16 --- /dev/null +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.html @@ -0,0 +1,29 @@ + + + {{#with character}} + + + + Export Character to Improved Initiative + + + + + Features + + + Attacks + + + Description + + + Copy to Clipboard + + + + Done + + {{/with}} + + diff --git a/rpg-docs/client/views/character/export/exportDialog/exportDialog.js b/rpg-docs/client/views/character/export/exportDialog/exportDialog.js new file mode 100644 index 000000000..e961c33d4 --- /dev/null +++ b/rpg-docs/client/views/character/export/exportDialog/exportDialog.js @@ -0,0 +1,45 @@ +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 && copyTextarea.select(); + try { + var successful = document.execCommand("copy"); + var msg = successful ? "successful" : "unsuccessful"; + console.log("Copying text command was " + msg); + } catch (err) { + console.log("Oops, unable to copy"); + } + }, + "click .doneButton": function(event, instance){ + popDialogStack(); + }, +}) diff --git a/rpg-docs/client/views/character/export/improvedInitiativeJson.js b/rpg-docs/client/views/character/export/improvedInitiativeJson.js new file mode 100644 index 000000000..d5e3298ef --- /dev/null +++ b/rpg-docs/client/views/character/export/improvedInitiativeJson.js @@ -0,0 +1,191 @@ +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": [], + "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"); + return { + "immunities": multipliers["0"] || [], + "resistances": multipliers["0.5"] || [], + "weaknesses": multipliers["2"] || [], + }; +} + +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: "", + })); +}