diff --git a/rpg-docs/Model/Character/Buffs.js b/rpg-docs/Model/Character/Buffs.js
index b2a367c2..e6e00710 100644
--- a/rpg-docs/Model/Character/Buffs.js
+++ b/rpg-docs/Model/Character/Buffs.js
@@ -23,7 +23,7 @@ Schemas.Buff = new SimpleSchema({
type: {
type: String,
allowedValues: [
- "inate",
+ "inate", //this should be "innate", but changing it could be problematic
"custom",
],
},
@@ -42,12 +42,26 @@ Schemas.Buff = new SimpleSchema({
allowedValues: _.pluck(colorOptions, "key"),
defaultValue: "q",
},
+ appliedBy: { //the charId of whoever applied the buff
+ type: String,
+ regEx: SimpleSchema.RegEx.Id,
+ },
+ appliedByDetails: {//the name and collection of the thing that applied the buff
+ type: Object,
+ optional: true,
+ },
+ "appliedByDetails.name": {
+ type: String,
+ },
+ "appliedByDetails.collection": {
+ type: String,
+ },
});
Buffs.attachSchema(Schemas.Buff);
Buffs.attachBehaviour("softRemovable");
-makeParent(Buffs, ["name", "enabled"]); //parents of effects
+makeParent(Buffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies
Buffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
Buffs.deny(CHARACTER_SUBSCHEMA_DENY);
diff --git a/rpg-docs/Model/Character/Characters.js b/rpg-docs/Model/Character/Characters.js
index 789b4c9e..0fabadc0 100644
--- a/rpg-docs/Model/Character/Characters.js
+++ b/rpg-docs/Model/Character/Characters.js
@@ -533,6 +533,7 @@ if (Meteor.isServer){
Attacks .remove({charId: character._id});
Buffs .remove({charId: character._id});
Classes .remove({charId: character._id});
+ CustomBuffs .remove({charId: character._id});
Effects .remove({charId: character._id});
Experiences .remove({charId: character._id});
Features .remove({charId: character._id});
diff --git a/rpg-docs/Model/Character/Conditions.js b/rpg-docs/Model/Character/Conditions.js
new file mode 100644
index 00000000..ce894fe1
--- /dev/null
+++ b/rpg-docs/Model/Character/Conditions.js
@@ -0,0 +1,42 @@
+Conditions = new Mongo.Collection("conditions");
+
+Schemas.Conditions = new SimpleSchema({
+ charId: {
+ type: String,
+ regEx: SimpleSchema.RegEx.Id,
+ index: 1,
+ },
+ name: {
+ type: String,
+ optional: true,
+ trim: false,
+ },
+ description: {
+ type: String,
+ optional: true,
+ trim: false,
+ },
+ "lifeTime.total": {
+ type: Number,
+ defaultValue: 0, //0 is infinite
+ min: 0,
+ },
+ "lifeTime.spent": {
+ type: Number,
+ defaultValue: 0,
+ min: 0,
+ },
+ color: {
+ type: String,
+ allowedValues: _.pluck(colorOptions, "key"),
+ defaultValue: "q",
+ },
+});
+
+Conditions.attachSchema(Schemas.Conditions);
+
+Conditions.attachBehaviour("softRemovable");
+makeParent(Conditions, ["name"]); //parents of effects, attacks, proficiencies
+
+Conditions.allow(CHARACTER_SUBSCHEMA_ALLOW);
+Conditions.deny(CHARACTER_SUBSCHEMA_DENY);
diff --git a/rpg-docs/Model/Character/CustomBuffs.js b/rpg-docs/Model/Character/CustomBuffs.js
new file mode 100644
index 00000000..7a766b01
--- /dev/null
+++ b/rpg-docs/Model/Character/CustomBuffs.js
@@ -0,0 +1,53 @@
+CustomBuffs = new Mongo.Collection("customBuffs");
+
+Schemas.CustomBuff = new SimpleSchema({
+ charId: {
+ type: String,
+ regEx: SimpleSchema.RegEx.Id,
+ index: 1,
+ },
+ name: {
+ type: String,
+ optional: true,
+ trim: false,
+ },
+ description: {
+ type: String,
+ optional: true,
+ trim: false,
+ },
+ target: {
+ type: String,
+ allowedValues: [
+ "self",
+ "others",
+ "both"
+ ],
+ defaultValue: "self",
+ },
+ enabled: {
+ type: Boolean,
+ autoValue: function(){
+ return false;
+ //enabled is ALWAYS false on these, so that its children are also not enabled, so that the buff templates have no effects.
+ },
+ },
+ "lifeTime.total": {
+ type: Number,
+ defaultValue: 0, //0 is infinite
+ min: 0,
+ },
+ //the id of the feature, buff or item that creates this buff
+ parent: {
+ type: Schemas.Parent,
+ },
+});
+
+CustomBuffs.attachSchema(Schemas.CustomBuff);
+
+CustomBuffs.attachBehaviour("softRemovable");
+makeParent(CustomBuffs, ["name", "enabled"]); //parents of effects, attacks, proficiencies. Since this represents a template, "enabled" is always false.
+makeChild(CustomBuffs); //children of lots of things
+
+CustomBuffs.allow(CHARACTER_SUBSCHEMA_ALLOW);
+CustomBuffs.deny(CHARACTER_SUBSCHEMA_DENY);
diff --git a/rpg-docs/client/globalHelpers/openParentDialog.js b/rpg-docs/client/globalHelpers/openParentDialog.js
index 139428fe..b220a652 100644
--- a/rpg-docs/client/globalHelpers/openParentDialog.js
+++ b/rpg-docs/client/globalHelpers/openParentDialog.js
@@ -18,6 +18,9 @@ openParentDialog = function({
} else if (parent.collection === "Spells") {
template = "spellDialog";
data = {spellId: parent.id};
+ } else if (parent.collection === "Buffs") {
+ template = "buffDialog";
+ data = {buffId: parent.id};
}
pushDialogStack({template, data, element, returnElement, callback});
};
diff --git a/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.html b/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.html
new file mode 100644
index 00000000..12634ba8
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Apply Buff
+
+
+
+
+ {{> characterPicker selfId=buff.charId includeSelf=canApplyToSelf writableOnly=true}}
+
+
+
+ {{#if buff.description}}
+
{{#markdown}}{{evaluateString buff.charId buff.description}}{{/markdown}}
+
+ {{/if}}
+ {{> effectsViewList charId=buff.charId parentId=buff._id}}
+ {{> proficiencyViewList charId=buff.charId parentId=buff._id}}
+ {{> attacksViewList charId=buff.charId parentId=buff._id}}
+
+
+
+
+
diff --git a/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.js b/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.js
new file mode 100644
index 00000000..afed51f6
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/applyBuffDialog/applyBuffDialog.js
@@ -0,0 +1,32 @@
+Template.applyBuffDialog.onCreated(function(){
+ this.selectedTarget = new ReactiveVar("default");
+});
+
+Template.applyBuffDialog.helpers({
+ cantApply: function() {
+ return this.buff.target === "others" && Template.instance().selectedTarget.get() === "default"; //this is the only case where we can't apply a buff
+ },
+ canApplyToSelf: function() {
+ return this.buff.target !== "others"; //i.e. it is "self" or "both"
+ },
+});
+
+Template.applyBuffDialog.events({
+ "iron-select .characterPicker": function(event){
+ var detail = event.originalEvent.detail;
+ var value = detail.item.getAttribute("name");
+ Template.instance().selectedTarget.set(value);
+ },
+ "click #applyButton": function(event, instance){
+ var targetId = Template.instance().selectedTarget.get();
+ if (targetId === "default") {
+ if (this.buff.target === "others") return; //since we have "Select a character" selected
+ targetId = this.buff.charId; //otherwise, the default is to target self
+ }
+
+ popDialogStack(targetId);
+ },
+ "click #cancelButton": function(event, instance){
+ popDialogStack();
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html
index 165dd377..6bd27c7e 100644
--- a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html
+++ b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.html
@@ -1,15 +1,23 @@
{{#with buff}}
- {{#baseDialog title=name class=colorClass hideEdit=true}}
+ {{#baseDialog title=name class="white" hideColor=true startEditing=true editOnly=true}}
+ {{> buffDetails}}
+ {{else}}
{{> buffDetails}}
{{/baseDialog}}
{{/with}}
+
+ {{appliedBy}}
+
+
{{#if description}}
- {{evaluateString charId description}}
+ {{#markdown}}{{evaluateString charId description}}{{/markdown}}
+
{{/if}}
-
{{> effectsViewList charId=charId parentId=_id}}
+ {{> proficiencyViewList charId=charId parentId=_id}}
+ {{> attacksViewList charId=charId parentId=_id}}
diff --git a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js
index be2e6d18..36fbb6e2 100644
--- a/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js
+++ b/rpg-docs/client/views/character/buffs/buffDialog/buffDialog.js
@@ -1,5 +1,50 @@
+Template.buffDialog.onCreated(function(){
+ var buff = Buffs.findOne(this.buffId);
+ Meteor.subscribe("singleCharacterName", buff.charId); //so we can access the names of public characters
+});
+
Template.buffDialog.helpers({
buff: function(){
return Buffs.findOne(this.buffId);
},
});
+
+Template.buffDialog.events({
+ "click #deleteButton": function(event, instance){
+ Buffs.softRemoveNode(instance.data.buffId);
+ popDialogStack();
+ },
+});
+
+const typeDict = {
+ "Features": "feature",
+ "Items": "item",
+ "Spells": "spell",
+}; //really, we should only need these three
+
+Template.buffDetails.helpers({
+ appliedBy: function() {
+ if (this.type == "inate") {
+ return "Innate.";
+ } else {
+ var myName = Characters.findOne(this.charId).name;
+ var applierCharacter = Characters.findOne(this.appliedBy) || {name: "???"}
+ // "???" indicates that either we do not have read access to the buff-giver, or that the buff-giver does not exist.
+
+ if (applierCharacter.name === myName) {
+ var charName = "your "
+ } else {
+ if (applierCharacter.name && applierCharacter.name[applierCharacter.name.length - 1] === 's') {
+ var charName = applierCharacter.name + "' ";
+ } else {
+ var charName = applierCharacter.name + "'s ";
+ }
+ }
+
+ var type = typeDict[this.appliedByDetails.collection] + " ";
+ var applierThing = this.appliedByDetails.name;
+
+ return "Applied by " + charName + type + applierThing + ".";
+ }
+ },
+});
\ No newline at end of file
diff --git a/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.html b/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.html
new file mode 100644
index 00000000..cb6d1b66
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.html
@@ -0,0 +1,15 @@
+
+
+
+ {{buff.name}}
+
+
+ {{#if canEditCharacter buff.charId}}
+
+
+ {{/if}}
+
+
diff --git a/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.js b/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.js
new file mode 100644
index 00000000..23f1d9ed
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/buffListItem/buffListItem.js
@@ -0,0 +1,21 @@
+Template.buffListItem.helpers({
+ name: function() {
+ return this.buff.name
+ }
+});
+
+Template.buffListItem.events({
+ "click .buffListItem": function(event){
+ var buffId = this.buff._id;
+ var charId = this.buff.charId;
+ pushDialogStack({
+ template: "buffDialog",
+ data: {buffId: buffId, charId: charId},
+ element: event.currentTarget,
+ });
+ },
+ "tap .deleteButton": function(event){
+ event.stopPropagation();
+ Buffs.remove(this.buff._id);
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.css b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.css
new file mode 100644
index 00000000..93345d5c
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.css
@@ -0,0 +1,11 @@
+.condition-library-dialog .item.selected {
+ background-color: #e4e4e4;
+}
+
+.condition-library-dialog table {
+ border-collapse: collapse;
+}
+
+.condition-library-dialog .library-condition td, tr {
+ position: relative;
+}
diff --git a/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.html b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.html
new file mode 100644
index 00000000..c295e596
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.html
@@ -0,0 +1,34 @@
+
+
+
+
+
+ Conditions
+
+
+
+
+
+
+
+
+ |
+ {{conditionName condition}}
+
+ |
+
+
diff --git a/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.js b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.js
new file mode 100644
index 00000000..bb1b5a05
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionLibraryDialog/conditionLibraryDialog.js
@@ -0,0 +1,166 @@
+Template.conditionLibraryDialog.onCreated(function(){
+ this.selectedCondition = new ReactiveVar();
+});
+
+Template.conditionLibraryDialog.helpers({
+ conditions: function(){
+ return Object.keys(LIBRARY_CONDITIONS)
+ },
+ isSelected(condition){
+ const selected = Template.instance().selectedCondition.get();
+ return selected && selected === condition;
+ },
+});
+
+Template.conditionLibraryDialog.events({
+ "click .cancelButton": function(event, template){
+ popDialogStack();
+ },
+ "click .okButton": function(event, template){
+ popDialogStack(template.selectedCondition.get());
+ },
+ "click .library-condition": function(event, template){
+ template.selectedCondition.set(this.condition);
+ },
+ "click #backButton": function(event, template){
+ popDialogStack();
+ },
+});
+
+Template.libraryCondition.helpers({
+ conditionName: function(name){
+ return LIBRARY_CONDITIONS[name].buff.name;
+ },
+})
+
+
+LIBRARY_CONDITIONS = {
+ //Conditions
+ blind: {
+ buff: {
+ name: "Blind",
+ description: "A blinded creature can’t see and automatically fails any ability check that requires sight.\n\nAttack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage.",
+ },
+ },
+
+ deaf: {
+ buff: {
+ name: "Deaf",
+ description: "A deafened creature can’t hear and automatically fails any ability check that requires hearing.",
+ },
+ },
+
+ frightened: {
+ buff: {
+ name: "Frightened",
+ description: "A frightened creature has disadvantage on ability checks and attack rolls while the source of its fear is within line of sight.\n\nThe creature can’t willingly move closer to the source of its fear.",
+ }
+ },
+
+ grappled: {
+ buff:{
+ name: "Grappled",
+ description: "A grappled creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.\n\nThe condition ends if the grappler is incapacitated.\n\nThe condition also ends if an effect removes the grappled creature from the reach of the grappler or grappling effect, such as when a creature is hurled away by the thunder wave spell.",
+ },
+ },
+
+ incapacitated: {
+ buff: {
+ name: "Incapacitated",
+ description: "An incapacitated creature can’t take actions or reactions.",
+ }
+ },
+
+ invisible: {
+ buff: {
+ name: "Invisible",
+ description: "An invisible creature is impossible to see without the aid of magic or a special sense. For the purpose of hiding, the creature is heavily obscured. The creature’s location can be detected by any noise it makes or any tracks it leaves.\n\nAttack rolls against the creature have disadvantage, and the creature’s attack rolls have advantage.",
+ }
+ },
+
+ paralyzed: {
+ buff: {
+ name: "Paralyzed",
+ description: "A paralyzed creature is **incapacitated** and can’t move or speak.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
+ },
+ },
+
+ petrified: {
+ buff: {
+ name: "Petrified",
+ description: "A petrified creature is transformed, along with any nonmagical object it is wearing or carrying, into a solid inanimate substance (usually stone). Its weight increases by a factor of ten, and it ceases aging.\n\nA petrified creature is **incapacitated** and can’t move or speak, and is unaware of its surroundings.\n\nAttack rolls against the creature have advantage.\n\nThe creature is immune to poison and disease, although a poison or disease already in its system is suspended, not neutralized.",
+ },
+ },
+
+ poisoned: {
+ buff: {
+ name: "Poisoned",
+ description: "A poisoned creature has disadvantage on attack rolls and ability checks.",
+ },
+ },
+
+ prone: {
+ buff: {
+ name: "Prone",
+ description: "A prone creature’s only movement option is to crawl, unless it stands up and thereby ends the condition.\n\nThe creature has disadvantage on attack rolls.\n\nAn attack roll against the creature has advantage if the attacker is within 5 feet of the creature. Otherwise, the attack roll has disadvantage.",
+ }
+ },
+
+ restrained: {
+ buff: {
+ name: "Restrained",
+ description: "A restrained creature’s speed becomes 0, and it can’t benefit from any bonus to its speed.\n\nAttack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage.\n\nThe creature has disadvantage on Dexterity saving throws.",
+ },
+ },
+
+ stunned: {
+ buff: {
+ name: "Stunned",
+ description: "A stunned creature is **incapacitated**, can’t move, and can speak only falteringly\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.",
+ },
+ },
+
+ unconscious: {
+ buff: {
+ name: "Unconscious",
+ description: "An unconscious creature is **incapacitated**, can’t move or speak, and is unaware of its surroundings.\n\nThe creature drops whatever it’s holding and falls **prone**.\n\nThe creature automatically fails Strength and Dexterity saving throws.\n\nAttack rolls against the creature have advantage.\n\nAny attack that hits the creature is a critical hit if the attacker is within 5 feet of the creature.",
+ },
+ },
+
+ exhaustion1: {
+ buff: {
+ name: "Exhaustion - 1",
+ description: "Disadvantage on ability checks\n\nFinishing a long rest reduces a creature’s exhaustion level by 1, provided that the creature has also ingested some food and drink.",
+ },
+ },
+ exhaustion2: {
+ buff: {
+ name: "Exhaustion - 2",
+ description: "Speed halved",
+ },
+ },
+ exhaustion3: {
+ buff: {
+ name: "Exhaustion - 3",
+ description: "Disadvantage on attack rolls and saving throws",
+ },
+ },
+ exhaustion4: {
+ buff: {
+ name: "Exhaustion - 4",
+ description: "Hit point maximum halved",
+ },
+ },
+ exhaustion5: {
+ buff: {
+ name: "Exhaustion - 5",
+ description: "Speed reduced to 0",
+ },
+ },
+ exhaustion6: {
+ buff: {
+ name: "Exhaustion - 6",
+ description: "You have died of exhaustion",
+ },
+ },
+};
diff --git a/rpg-docs/client/views/character/buffs/conditionView/conditionView.html b/rpg-docs/client/views/character/buffs/conditionView/conditionView.html
new file mode 100644
index 00000000..4dff87ab
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionView/conditionView.html
@@ -0,0 +1,15 @@
+
+
+
+ {{condition.name}}
+
+
+ {{#if canEditCharacter condition.charId}}
+
+
+ {{/if}}
+
+
diff --git a/rpg-docs/client/views/character/buffs/conditionView/conditionView.js b/rpg-docs/client/views/character/buffs/conditionView/conditionView.js
new file mode 100644
index 00000000..8d22a0ea
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionView/conditionView.js
@@ -0,0 +1,15 @@
+Template.conditionView.events({
+ "click .conditionView": function(event){
+ var condition = this.condition;
+ var charId = Template.parentData()._id;
+ pushDialogStack({
+ template: "conditionViewDialog",
+ data: {condition: condition},
+ element: event.currentTarget,
+ });
+ },
+ "tap .deleteButton": function(event){
+ event.stopPropagation();
+ Conditions.remove(this.condition._id);
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.html b/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.html
new file mode 100644
index 00000000..9800cc25
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.html
@@ -0,0 +1,14 @@
+
+ {{#baseDialog title=condition.name class="white" hideColor=true startEditing=true editOnly=true}}}
+ {{> conditionDetails condition=condition}}
+ {{else}}
+ {{> conditionDetails condition=condition}}
+ {{/baseDialog}}
+
+
+
+ {{#if condition.description}}
+ {{#markdown}}{{evaluateString condition.charId condition.description}}{{/markdown}}
+ {{/if}}
+ {{> effectsViewList charId=condition.charId parentId=condition._id}}
+
\ No newline at end of file
diff --git a/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.js b/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.js
new file mode 100644
index 00000000..da029186
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/conditionViewDialog/conditionViewDialog.js
@@ -0,0 +1,6 @@
+Template.conditionViewDialog.events({
+ "click #deleteButton": function(event, instance){
+ Conditions.remove(instance.data.condition._id);
+ popDialogStack();
+ },
+});
\ No newline at end of file
diff --git a/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.html b/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.html
new file mode 100644
index 00000000..59ada714
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.html
@@ -0,0 +1,29 @@
+
+ {{#baseEditDialog title=buff.name hideColor=true}}
+
+
+
+
+
+
+
+ Self only
+
+
+ Others only
+
+
+ Both
+
+
+
+
+
+
+
+
+ {{> effectsEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
+ {{> attackEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId name=name enabled=false}}
+ {{> proficiencyEditList parentId=buff._id parentCollection="CustomBuffs" charId=buff.charId enabled=false}}
+ {{/baseEditDialog}}
+
\ No newline at end of file
diff --git a/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.js b/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.js
new file mode 100644
index 00000000..76329df4
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffEdit/customBuffEdit.js
@@ -0,0 +1,47 @@
+Template.customBuffEdit.helpers({
+ buff(){
+ return CustomBuffs.findOne(this.customBuffId);
+ },
+});
+
+const debounce = (f) => _.debounce(f, 300);
+
+Template.customBuffEdit.events({
+ "input #buffNameInput": debounce(function(event){
+ const input = event.currentTarget;
+ var name = input.value;
+ if (!name){
+ input.invalid = true;
+ input.errorMessage = "Name is required";
+ } else {
+ input.invalid = false;
+ CustomBuffs.update(this.customBuffId, {
+ $set: {name: name}
+ }, {
+ removeEmptyStrings: false,
+ trimStrings: false,
+ });
+ }
+ }),
+ "input #buffDescriptionInput": debounce(function(event){
+ var description = event.currentTarget.value;
+ CustomBuffs.update(this.customBuffId, {
+ $set: {description: description}
+ }, {
+ removeEmptyStrings: false,
+ trimStrings: false,
+ });
+ }),
+ "iron-select .target-dropdown": function(event){
+ var detail = event.originalEvent.detail;
+ var value = detail.item.getAttribute("name");
+ const buff = CustomBuffs.findOne(this.customBuffId);
+ if (value === buff.target) return;
+ CustomBuffs.update(this.customBuffId, {$set: {target: value}});
+ },
+ "click #deleteButton": function(event, instance){
+ CustomBuffs.softRemoveNode(instance.data.customBuffId);
+ GlobalUI.deletedToast(instance.data.customBuffId, "Buffs", "Buff");
+ popDialogStack();
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.html b/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.html
new file mode 100644
index 00000000..f5abb8eb
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.html
@@ -0,0 +1,30 @@
+
+
+ {{#if buffs.count}}
+
+
+ Buffs
+
+
+ {{#each buff in buffs}}
+ {{> customBuffEditListItem buff=buff}}
+ {{/each}}
+
+
+ {{/if}}
+
+ Add Buff
+
+
+
+
+
+ {{> customBuffView buff=buff}}
+
+
+
diff --git a/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.js b/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.js
new file mode 100644
index 00000000..c9f1e752
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffEditList/customBuffEditList.js
@@ -0,0 +1,41 @@
+Template.customBuffEditList.helpers({
+ buffs: function(){
+ var selector = {
+ "parent.id": this.parentId,
+ "charId": this.charId,
+ };
+ return CustomBuffs.find(selector);
+ }
+});
+
+Template.customBuffEditList.events({
+ "tap #addBuffButton": function(event, instance){
+ if (!_.isBoolean(this.enabled)) {
+ this.enabled = true;
+ }
+ const customBuffId = CustomBuffs.insert({
+ name: this.name || "New Buff",
+ charId: this.charId,
+ parent: {
+ id: this.parentId,
+ collection: this.parentCollection,
+ },
+ });
+ pushDialogStack({
+ template: "customBuffEdit",
+ data: {customBuffId},
+ element: event.currentTarget,
+ returnElement: () => instance.find(`tr.buff[data-id='${customBuffId}']`),
+ });
+ },
+});
+
+Template.customBuffEditListItem.events({
+ "tap .edit-buff": function(event, template){
+ pushDialogStack({
+ template: "customBuffEdit",
+ data: {customBuffId: this.buff._id},
+ element: event.currentTarget.parentElement.parentElement,
+ });
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.html b/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.html
new file mode 100644
index 00000000..09971a01
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.html
@@ -0,0 +1,8 @@
+
+ {{buff.name}}
+
+ {{#if canEditCharacter buff.charId}}
+
Apply{{toSelf}}
+ {{/if}}
+
+
diff --git a/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.js b/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.js
new file mode 100644
index 00000000..fdc30712
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffView/customBuffView.js
@@ -0,0 +1,82 @@
+const applyBuff = function(targetId, buff) {
+ var parent = global[buff.parent.collection].findOne(buff.parent.id);
+
+ //insert new buff
+ newBuffId = Buffs.insert({
+ charId: targetId,
+ name: buff.name,
+ description: buff.description,
+ lifeTime: {total: buff.lifeTime.total},
+ type: "custom",
+
+ appliedBy: buff.charId,
+ appliedByDetails: {
+ name: parent.name,
+ collection: buff.parent.collection,
+ },
+ });
+
+ //insert children
+ Attacks.find({"parent.id": buff._id}).forEach(function(doc){
+ temp = _.clone(doc);
+ temp.parent.id = newBuffId;
+ temp.parent.collection = "Buffs";
+ delete temp._id;
+
+ Attacks.insert(temp);
+ });
+ Effects.find({"parent.id": buff._id}).forEach(function(doc){
+ temp = _.clone(doc);
+ temp.parent.id = newBuffId;
+ temp.parent.collection = "Buffs";
+ delete temp._id;
+
+ Effects.insert(temp);
+ });
+ Proficiencies.find({"parent.id": buff._id}).forEach(function(doc){
+ temp = _.clone(doc);
+ temp.parent.id = newBuffId;
+ temp.parent.collection = "Buffs";
+ delete temp._id;
+
+ Proficiencies.insert(temp);
+ });
+
+ let target;
+ if (targetId == buff.charId) {
+ target = "self";
+ } else {
+ target = Characters.findOne(targetId) || {};
+ target = target && target.name || "target"
+ }
+ GlobalUI.toast(`${buff.name || "Buff"} applied to ${target}`);
+};
+
+Template.customBuffView.helpers({
+ toSelf: function() {
+ if (this.buff.target === "self") {
+ return " to self";
+ } else {
+ return "";
+ }
+ }
+});
+
+Template.customBuffView.events({
+ "click .apply-buff-button": function(){
+ if (this.buff.target !== "self") {
+ pushDialogStack({
+ template: "applyBuffDialog",
+ data: {buff: this.buff},
+ element: event.currentTarget,
+ callback: (targetId) => {
+ if (!targetId) return;
+ applyBuff(targetId, this.buff);
+ },
+ });
+ } else {
+ var targetId = this.buff.charId;
+ applyBuff(targetId, this.buff);
+ }
+ },
+});
diff --git a/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.html b/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.html
new file mode 100644
index 00000000..7aae4d4c
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.html
@@ -0,0 +1,14 @@
+
+ {{#if buffs.count}}
+
+
+ Buffs
+
+ {{#each buff in buffs}}
+
+ {{> customBuffView buff=buff}}
+
+ {{/each}}
+
+ {{/if}}
+
diff --git a/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.js b/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.js
new file mode 100644
index 00000000..2efa4dd4
--- /dev/null
+++ b/rpg-docs/client/views/character/buffs/customBuffViewList/customBuffViewList.js
@@ -0,0 +1,9 @@
+Template.customBuffViewList.helpers({
+ buffs: function(){
+ var selector = {
+ "parent.id": this.parentId,
+ "charId": this.charId,
+ };
+ return CustomBuffs.find(selector);
+ }
+});
diff --git a/rpg-docs/client/views/character/features/featureDialog/featureDialog.html b/rpg-docs/client/views/character/features/featureDialog/featureDialog.html
index 11219e2b..c73a988c 100644
--- a/rpg-docs/client/views/character/features/featureDialog/featureDialog.html
+++ b/rpg-docs/client/views/character/features/featureDialog/featureDialog.html
@@ -36,7 +36,9 @@
{{> effectsViewList charId=charId parentId=_id}}
{{> proficiencyViewList charId=charId parentId=_id}}
- {{> attacksViewList charId=charId parentId=_id}}
+ {{> attacksViewList charId=charId parentId=_id}}
+ {{> customBuffViewList charId=charId parentId=_id}}
+
@@ -77,5 +79,6 @@
{{> effectsEditList parentId=_id parentCollection="Features" charId=charId name=name enabled=enabled}}
{{> proficiencyEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled}}
- {{> attackEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled name=name}}
+ {{> attackEditList parentId=_id parentCollection="Features" charId=charId enabled=enabled name=name}}
+ {{> customBuffEditList parentId=_id parentCollection="Features" charId=charId}}
diff --git a/rpg-docs/client/views/character/features/features.html b/rpg-docs/client/views/character/features/features.html
index ca7f95b9..b7157b11 100644
--- a/rpg-docs/client/views/character/features/features.html
+++ b/rpg-docs/client/views/character/features/features.html
@@ -81,6 +81,7 @@
{{#if hasCharacters (evaluateShortString charId description)}}
{{#markdown}}{{evaluateShortString charId description}}{{/markdown}}
+ {{> customBuffViewList charId=charId parentId=_id}}
{{/if}}
{{#if hasUses}}
@@ -159,4 +160,4 @@
-
\ No newline at end of file
+
diff --git a/rpg-docs/client/views/character/inventory/inventory.html b/rpg-docs/client/views/character/inventory/inventory.html
index 3f8cf590..cf823d5f 100644
--- a/rpg-docs/client/views/character/inventory/inventory.html
+++ b/rpg-docs/client/views/character/inventory/inventory.html
@@ -28,16 +28,16 @@
{{> carryCapacityBar}}
- {{#if encumberedBuffs.count}}
+ {{#if encumberedConditions.count}}
- {{#each encumberedBuffs}}
+ {{#each condition in encumberedConditions}}
-
+
- {{name}}
+ {{condition.name}}
diff --git a/rpg-docs/client/views/character/inventory/inventory.js b/rpg-docs/client/views/character/inventory/inventory.js
index bddd2014..ac31e39f 100644
--- a/rpg-docs/client/views/character/inventory/inventory.js
+++ b/rpg-docs/client/views/character/inventory/inventory.js
@@ -68,9 +68,8 @@ Template.inventory.helpers({
return weight;
},
encumberedBuffs: function(){
- return Buffs.find({
+ return Conditions.find({
charId: this._id,
- type: "inate",
name: {$in: [
"Encumbered",
"Heavily encumbered",
@@ -201,12 +200,10 @@ Template.inventory.events({
element: event.currentTarget.parentElement,
});
},
- "click .buff": function(event, instance){
- var buffId = this._id;
- var charId = Template.parentData()._id;
+ "click .condition": function(event, instance){
pushDialogStack({
- template: "buffDialog",
- data: {buffId: buffId, charId: charId},
+ template: "conditionViewDialogDialog",
+ data: {condition: this.condition},
element: event.currentTarget,
});
},
diff --git a/rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html b/rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html
index 13852b7c..50d5f614 100644
--- a/rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html
+++ b/rpg-docs/client/views/character/inventory/itemDialog/itemDialog.html
@@ -23,6 +23,7 @@
{{/if}}
{{> effectsViewList charId=charId parentId=_id}}
{{> attacksViewList charId=charId parentId=_id}}
+ {{> customBuffViewList charId=charId parentId=_id}}
@@ -65,10 +66,13 @@
{{> textareaBracketSuffix}}
+
{{> effectsEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
{{> attackEditList parentId=_id parentCollection="Items" charId=charId enabled=equipped name=name}}
+
+ {{> customBuffEditList parentId=_id parentCollection="Items" charId=charId}}
diff --git a/rpg-docs/client/views/character/spells/spellDialog/spellDialog.html b/rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
index aa23a17e..87546736 100644
--- a/rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
+++ b/rpg-docs/client/views/character/spells/spellDialog/spellDialog.html
@@ -38,6 +38,7 @@
{{#markdown}}{{evaluateSpellString charId parent.id description}}{{/markdown}}
{{> attacksViewList charId=charId parentId=_id}}
+ {{> customBuffViewList charId=charId parentId=_id}}
@@ -111,10 +112,12 @@
+
+ {{> customBuffEditList parentId=_id parentCollection="Spells" charId=charId}}
{{> attackEditList parentId=_id parentCollection="Spells" charId=charId enabled=true name=name isSpell=true}}
diff --git a/rpg-docs/client/views/character/stats/stats.html b/rpg-docs/client/views/character/stats/stats.html
index 3cde018b..e9c8736e 100644
--- a/rpg-docs/client/views/character/stats/stats.html
+++ b/rpg-docs/client/views/character/stats/stats.html
@@ -42,6 +42,34 @@
+
+
+
+
+
Conditions
+ {{#if canEditCharacter _id}}
+
+ {{/if}}
+
+
+
+ {{#each condition in conditions}}
+ {{>conditionView condition=condition}}
+ {{/each}}
+
+ {{#if buffs.count}}
+
+ {{/if}}
+
+ {{#each buff in buffs}}
+ {{>buffListItem buff=buff}}
+ {{/each}}
+
+
+
+
diff --git a/rpg-docs/client/views/character/stats/stats.js b/rpg-docs/client/views/character/stats/stats.js
index f38934b8..365136b8 100644
--- a/rpg-docs/client/views/character/stats/stats.js
+++ b/rpg-docs/client/views/character/stats/stats.js
@@ -1,3 +1,15 @@
+Template.stats.helpers({
+ conditions: function() {
+ return Conditions.find({charId: this._id});
+ },
+ buffs: function() {
+ var selector = {
+ "charId": this._id,
+ };
+ return Buffs.find(selector);
+ },
+})
+
Template.stats.events({
"click .stat-card": function(event, instance){
var charId = instance.data._id;
@@ -65,4 +77,17 @@ Template.stats.events({
element: event.currentTarget.parentElement.parentElement,
});
},
+ "click #addCondition": function(event, template){
+ pushDialogStack({
+ template: "conditionLibraryDialog",
+ element: event.currentTarget,
+ callback: (result) => {
+ if (!result) {
+ return;
+ }
+ else Meteor.call("giveCondition", this._id, result)
+ },
+ //returnElement: () => $(`[data-id='${itemId}']`).get(0),
+ })
+ },
});
diff --git a/rpg-docs/client/views/characterList/characterPicker/characterPicker.css b/rpg-docs/client/views/characterList/characterPicker/characterPicker.css
new file mode 100644
index 00000000..7062c38f
--- /dev/null
+++ b/rpg-docs/client/views/characterList/characterPicker/characterPicker.css
@@ -0,0 +1,17 @@
+.characterPicker .character-name {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.characterPicker .partyHead {
+ font-weight: 500;
+}
+
+.characterPicker .partyHead iron-icon {
+ transition: transform 0.3s ease;
+}
+
+.characterPicker .partyHead iron-icon.open {
+ transform: rotate(90deg);
+}
diff --git a/rpg-docs/client/views/characterList/characterPicker/characterPicker.html b/rpg-docs/client/views/characterList/characterPicker/characterPicker.html
new file mode 100644
index 00000000..2f43a12a
--- /dev/null
+++ b/rpg-docs/client/views/characterList/characterPicker/characterPicker.html
@@ -0,0 +1,34 @@
+
+
+ {{#if selfId}}{{#if includeSelf}}
+
+
+ Self
+
+
+ {{/if}}{{/if}}
+ {{#each charactersWithNoParty}}
+
+
+ {{name}}
+
+
+ {{/each}}
+ {{#each parties}}
+
+
+
+ {{name}}
+
+
+ {{#each charactersInParty}}
+
+
+ {{name}}
+
+
+ {{/each}}
+
+ {{/each}}
+
+
diff --git a/rpg-docs/client/views/characterList/characterPicker/characterPicker.js b/rpg-docs/client/views/characterList/characterPicker/characterPicker.js
new file mode 100644
index 00000000..d8c094a5
--- /dev/null
+++ b/rpg-docs/client/views/characterList/characterPicker/characterPicker.js
@@ -0,0 +1,53 @@
+Template.characterPicker.onCreated(function() {
+ this.subscribe("characterList");
+ this.openedParties = new ReactiveVar(new Set());
+});
+
+Template.characterPicker.helpers({
+ parties() {
+ return Parties.find(
+ {owner: Meteor.userId()},
+ {sort: {name: 1}},
+ );
+ },
+ charactersInParty() {
+ var userId = Meteor.userId();
+ var selector = {
+ _id: {$in: this.characters, $ne: this.selfId},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ };
+ if (this.writableOnly) {
+ selector.$or = [{writers: userId}, {owner: userId}];
+ }
+ return Characters.find(selector,{sort: {name: 1}});
+ },
+ charactersWithNoParty() {
+ var userId = Meteor.userId();
+ var charArrays = Parties.find({owner: userId}).map(p => p.characters);
+ var partyChars = _.uniq(_.flatten(charArrays));
+ var selector = {
+ _id: {$nin: partyChars, $ne: this.selfId},
+ $or: [{readers: userId}, {writers: userId}, {owner: userId}],
+ };
+ if (this.writableOnly) {
+ selector.$or = [{writers: userId}, {owner: userId}];
+ }
+ return Characters.find(selector, {sort: {name: 1}});
+ },
+ isOpen(id) {
+ var openedParties = Template.instance().openedParties.get();
+ return openedParties.has(id);
+ },
+});
+
+Template.characterPicker.events({
+ "click .partyHead": function(event, instance){
+ var openedParties = instance.openedParties.get();
+ if (openedParties.has(this._id)){
+ openedParties.delete(this._id);
+ } else {
+ openedParties.add(this._id);
+ }
+ instance.openedParties.set(openedParties);
+ },
+});
diff --git a/rpg-docs/client/views/characterList/characterSideList.js b/rpg-docs/client/views/characterList/characterSideList.js
index d27d0941..a1faad12 100644
--- a/rpg-docs/client/views/characterList/characterSideList.js
+++ b/rpg-docs/client/views/characterList/characterSideList.js
@@ -34,7 +34,6 @@ Template.characterSideList.helpers({
},
isOpen(id) {
var openedParties = Template.instance().openedParties.get();
- console.log(openedParties);
return openedParties.has(id);
},
});
diff --git a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.html b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.html
index 5eb08462..9515f55f 100644
--- a/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.html
+++ b/rpg-docs/client/views/paperTemplates/baseDialog/baseDialog.html
@@ -15,10 +15,12 @@
{{/unless}}
{{#unless hideColor}}
{{> colorDropdown}}
+ {{/unless}}
+ {{#unless editOnly}}
+
+
{{/unless}}
-
-
{{else}}
{{#if showEdit}}
{
const collections = [
- Attacks, Buffs, Classes, Effects, Experiences,
+ Attacks, Buffs, Classes, CustomBuffs, Effects, Experiences,
Features, Notes, Proficiencies, SpellLists, Spells,
Containers, Items,
];
diff --git a/rpg-docs/server/publications/singleCharacter.js b/rpg-docs/server/publications/singleCharacter.js
index 13632db3..771375b6 100644
--- a/rpg-docs/server/publications/singleCharacter.js
+++ b/rpg-docs/server/publications/singleCharacter.js
@@ -13,22 +13,40 @@ Meteor.publish("singleCharacter", function(characterId){
return [
Characters.find({_id: characterId}),
//get all the assets for this character including soft deleted ones
- Actions.find ({charId: characterId}, {removed: true}),
- Attacks.find ({charId: characterId}, {removed: true}),
- Buffs.find ({charId: characterId}, {removed: true}),
- Classes.find ({charId: characterId}, {removed: true}),
- Containers.find ({charId: characterId}, {removed: true}),
- Effects.find ({charId: characterId}, {removed: true}),
- Experiences.find ({charId: characterId}, {removed: true}),
- Features.find ({charId: characterId}, {removed: true}),
- Items.find ({charId: characterId}, {removed: true}),
- Notes.find ({charId: characterId}, {removed: true}),
- Spells.find ({charId: characterId}, {removed: true}),
- SpellLists.find ({charId: characterId}, {removed: true}),
- TemporaryHitPoints.find({charId: characterId}, {removed: true}),
- Proficiencies.find ({charId: characterId}, {removed: true}),
+ Actions.find ({charId: characterId}, {removed: true}),
+ Attacks.find ({charId: characterId}, {removed: true}),
+ Buffs.find ({charId: characterId}, {removed: true}),
+ Classes.find ({charId: characterId}, {removed: true}),
+ Conditions.find ({charId: characterId}, {removed: true}),
+ Containers.find ({charId: characterId}, {removed: true}),
+ CustomBuffs.find ({charId: characterId}, {removed: true}),
+ Effects.find ({charId: characterId}, {removed: true}),
+ Experiences.find ({charId: characterId}, {removed: true}),
+ Features.find ({charId: characterId}, {removed: true}),
+ Items.find ({charId: characterId}, {removed: true}),
+ Notes.find ({charId: characterId}, {removed: true}),
+ Spells.find ({charId: characterId}, {removed: true}),
+ SpellLists.find ({charId: characterId}, {removed: true}),
+ TemporaryHitPoints.find ({charId: characterId}, {removed: true}),
+ Proficiencies.find ({charId: characterId}, {removed: true}),
];
} else {
return [];
}
});
+
+Meteor.publish("singleCharacterName", function(characterId){
+ userId = this.userId;
+ var char = Characters.findOne({
+ _id: characterId,
+ $or: [
+ {readers: userId},
+ {writers: userId},
+ {owner: userId},
+ {"settings.viewPermission": "public"},
+ ],
+ });
+ if (char) {
+ return Characters.find(characterId, {fields:{"name": 1}});
+ }
+});
\ No newline at end of file