-
-
-
Combat Tracker
+
+
+
+
+
+
+
+
+
+
Combat Tracker
+
Harmless Key
-
Harmless Key
+
Try demo
-
Try demo
+
+
+
-
-
-
+
+
+
-
-
\ No newline at end of file
+ },
+};
+
diff --git a/src/mixins/dice.js b/src/mixins/dice.js
index ed7e069f6..05395cdf6 100644
--- a/src/mixins/dice.js
+++ b/src/mixins/dice.js
@@ -4,17 +4,16 @@ export const dice = {
data() {
return {
animateTrigger: false,
- rolled: 0
- }
+ rolled: 0,
+ };
},
computed: {
- ...mapGetters([
- "broadcast"
- ]),
+ ...mapGetters(["broadcast"]),
critSettings() {
- if(this.$store.getters.userSettings && this.$store.getters.userSettings.encounter) {
+ if (this.$store.getters.userSettings && this.$store.getters.userSettings.encounter) {
return this.$store.getters.userSettings.encounter.critical;
- } return undefined; // Default = undefined = roll twice
+ }
+ return undefined; // Default = undefined = roll twice
},
dice_types() {
return [
@@ -24,50 +23,64 @@ export const dice = {
{ value: 10, text: "d10", average: this.calcAverage(10) },
{ value: 12, text: "d12", average: this.calcAverage(12) },
];
- }
+ },
},
watch: {
animateTrigger() {
this.animateValue("roll", 0, this.rolled, 500);
this.rolled = 0;
- }
+ },
},
methods: {
...mapActions(["setRoll"]),
...mapActions("campaigns", ["set_share"]),
- rollD(e, d=20, n=1, m=0, title, entity_name=undefined, notify=false, advantage_disadvantage={}, share=null) {
+ rollD(
+ e,
+ d = 20,
+ n = 1,
+ m = 0,
+ title,
+ entity_name = undefined,
+ notify = false,
+ advantage_disadvantage = {},
+ share = null
+ ) {
m = parseInt(m); //Removes + from modifier
const add = (a, b) => a + b;
let throws = [];
let ignored = undefined;
-
+
//Check for advantage with advantage or disadvantage when a single d20 is rolled
- if(n === 1 && d === 20 && (e.shiftKey || e.ctrlKey || Object.keys(advantage_disadvantage).length > 0)) {
- if(e.shiftKey || e.ctrlKey) {
- const type = (e.shiftKey) ? "advantage" : "disadvantage";
+ if (
+ n === 1 &&
+ d === 20 &&
+ (e.shiftKey || e.ctrlKey || Object.keys(advantage_disadvantage).length > 0)
+ ) {
+ if (e.shiftKey || e.ctrlKey) {
+ const type = e.shiftKey ? "advantage" : "disadvantage";
advantage_disadvantage[type] = true;
}
//Only roll with advantage/disadvantage if only 1 is present they cancel eachother out
- if(Object.keys(advantage_disadvantage).length === 1) {
+ if (Object.keys(advantage_disadvantage).length === 1) {
n = 2;
}
} else {
advantage_disadvantage = {};
}
-
+
//Roll the dice
- for (var i=0; i < n; i++) {
- throws.push(Math.ceil(Math.random() * d))
+ for (var i = 0; i < n; i++) {
+ throws.push(Math.ceil(Math.random() * d));
}
//Roll with advantage/disadvantage
- if(Object.keys(advantage_disadvantage).length === 1) {
- let ignoredRoll = (throws[0] < throws[1]) ? 0 : 1; //ignore the lowest roll
-
+ if (Object.keys(advantage_disadvantage).length === 1) {
+ let ignoredRoll = throws[0] < throws[1] ? 0 : 1; //ignore the lowest roll
+
//With disadvantage, ignore the highest roll
- if(advantage_disadvantage.disadvantage) {
- ignoredRoll = (throws[0] > throws[1]) ? 0 : 1;
+ if (advantage_disadvantage.disadvantage) {
+ ignoredRoll = throws[0] > throws[1] ? 0 : 1;
}
ignored = throws[ignoredRoll];
@@ -75,33 +88,36 @@ export const dice = {
n = 1;
}
- let s = ''
+ let s = "";
if (Math.sign(m) >= 0) {
- s = '+'
+ s = "+";
}
const sumThrows = throws.reduce(add);
const sumTotal = sumThrows + parseInt(m);
- const showRoll = (m !== 0) ? `${n}d${d}${s}${m}` : `${n}d${d}`;
+ const showRoll = m !== 0 ? `${n}d${d}${s}${m}` : `${n}d${d}`;
const roll = {
title,
roll: showRoll,
+ n,
d,
+ s,
+ m,
mod: s + m,
throws: throws,
throwsTotal: sumThrows,
total: sumTotal,
advantage_disadvantage,
- ignored
- }
- if(entity_name) roll.entity_name = entity_name;
-
- if(notify) {
+ ignored,
+ };
+ if (entity_name) roll.entity_name = entity_name;
+
+ if (notify) {
let advantage;
- if(ignored) {
+ if (ignored) {
const type = Object.keys(advantage_disadvantage)[0].charAt(0).capitalize();
- const color = (type === "A") ? "green" : "red";
+ const color = type === "A" ? "green" : "red";
advantage = `
${type} ${ignored} `;
}
@@ -110,18 +126,20 @@ export const dice = {
`
${entity_name ? `${entity_name}: ` : ``}${title}
${roll.total}
-
-
`, {
- timeout: 3000,
- closeOnClick: true
- });
+
+
`,
+ {
+ timeout: 3000,
+ closeOnClick: true,
+ }
+ );
this.rolled = roll.total;
}
-
+
//Save the roll in the encounter "shares" object
- if(share && this.broadcast.live) {
+ if (share && this.broadcast.live) {
const key = Date.now() + Math.random().toString(36).substring(4);
- let share_roll = {...roll};
+ let share_roll = { ...roll };
// Remove unneeded properties
delete share_roll.throws;
@@ -129,243 +147,274 @@ export const dice = {
delete share_roll.d;
delete share_roll.mod;
delete share_roll.ignored;
- if(!roll.ignored) {
+ if (!roll.ignored) {
delete share_roll.advantage_disadvantage;
} else {
share_roll.advantage_disadvantage = Object.keys(advantage_disadvantage)[0].charAt(0);
}
-
+
let share_object = {
key,
type: "roll",
- notification: share_roll
- }
+ notification: share_roll,
+ };
+
+ if (share.encounter_id) share_object.encounter_id = share.encounter_id;
+ if (share.entity_key) share_object.entity_key = share.entity_key;
- if(share.encounter_id) share_object.encounter_id = share.encounter_id;
- if(share.entity_key) share_object.entity_key = share.entity_key;
-
// Share the roll
- this.set_share({ id: this.broadcast.live, share: share_object })
+ this.set_share({ id: this.broadcast.live, share: share_object });
}
this.setRoll(roll);
return roll;
},
- rollD6(n=1,m=0) {
- return this.rollD(6,n,m)
+ rollD6(n = 1, m = 0) {
+ return this.rollD(6, n, m);
},
- rollD8(n=1,m=0) {
- return this.rollD(8,n,m)
+ rollD8(n = 1, m = 0) {
+ return this.rollD(8, n, m);
},
- rollD10(n=1,m=0) {
- return this.rollD(10,n,m)
+ rollD10(n = 1, m = 0) {
+ return this.rollD(10, n, m);
},
- rollD20(n=1,m=0) {
- return this.rollD(20,n,m)
+ rollD20(n = 1, m = 0) {
+ return this.rollD(20, n, m);
},
- rollD100(n=1,m=0) {
- return this.rollD(100,n,m)
+ rollD100(n = 1, m = 0) {
+ return this.rollD(100, n, m);
},
/**
* Roll any spell or monster action
- *
- * @param {object} e Event, holds info for advantage/disadvantege
+ *
+ * @param {object} e Event, holds info for advantage/disadvantage
* @param {object} ability Full ability object
- * @param {object} config Holds configuration options {type, castLevel, casterLevel, toHitModifier, versatile}
- *
+ * @param {object} config Holds configuration options {type, cast_level, caster_level, toHitModifier, versatile}
+ *
* @returns {object}
*/
- rollAction(e, ability, config={}) {
+ rollAction(e, ability, config = {}) {
let returnRoll = {
- name: ability.name,
- actions: []
+ name: this.getAbilityName(ability, config),
+ actions: [],
};
- if(config.versatile !== undefined) {
- returnRoll.name = (config.versatile === 0) ?
- `${ability.name} (${ability.versatile_one || 'Option 1'})` :
- `${ability.name} (${ability.versatile_two || 'Option 2'})`;
+ if (config.option !== undefined) {
+ if (ability.options && ability.options.length) {
+ returnRoll.name = `${ability.name} (${config.option})`;
+ } else if (ability.versatile) {
+ returnRoll.name =
+ config.option === 0
+ ? `${ability.name} (${ability.versatile_one || "Option 1"})`
+ : `${ability.name} (${ability.versatile_two || "Option 2"})`;
+ }
}
// Check for advantage/disadvantage in the $event
- let advantage_object = (e.advantage_disadvantage) ? e.advantage_disadvantage : {};
- if(e.e.shiftKey) {
+ let advantage_object = e.advantage_disadvantage ? e.advantage_disadvantage : {};
+ if (e.e.shiftKey) {
advantage_object["advantage"] = true;
- }
- if(e.e.ctrlKey) {
+ }
+ if (e.e.ctrlKey) {
advantage_object["disadvantage"] = true;
}
const actions = ability.action_list; // All actions in the ability
- const scaleType = ability.level_scaling; // Only for spells
- const spellLevel = ability.level; // Only for spells
let i = 0;
// LOOP OVER ALL ACTIONS
- for(let action of actions) {
+ for (let action of actions) {
let type = action.type;
- let attack_bonus = action.attack_bonus || config.toHitModifier;
+ let attack_bonus = action.attack_bonus || config.attack_bonus;
let toHit = false;
let crit = false;
returnRoll.actions[i] = { type, rolls: [] };
// If the action type is a spell attack, melee weapon or ranged weapon, roll to hit
- if(type === 'melee_weapon' || type === 'ranged_weapon' || type === 'spell_attack') {
- returnRoll.actions[i].toHit = this.rollD(e.e, 20, 1, attack_bonus, `${ability.name} to hit`, undefined, false, advantage_object);
- if(returnRoll.actions[i].toHit.throwsTotal === 20) {
+ if (type === "melee_weapon" || type === "ranged_weapon" || type === "spell_attack") {
+ returnRoll.actions[i].toHit = this.rollD(
+ e.e,
+ 20,
+ 1,
+ attack_bonus,
+ `${ability.name} to hit`,
+ undefined,
+ false,
+ advantage_object
+ );
+ if (returnRoll.actions[i].toHit.throwsTotal >= 20) {
returnRoll.actions[i].crit = true;
crit = true;
}
toHit = true;
}
// For a saving throw set the ability and DC for display
- if(type === 'save') {
+ if (type === "save") {
returnRoll.actions[i].save_ability = action.save_ability;
- returnRoll.actions[i].save_dc = action.save_dc;
+ returnRoll.actions[i].save_dc = action.save_dc || 10;
}
- for(let modifier of action.rolls) {
- let damage_type = modifier.damage_type;
- let dice_type = modifier.dice_type;
- let dice_count = (crit && !this.critSettings) ? modifier.dice_count*2 : modifier.dice_count;
- let fixed_val = (modifier.fixed_val) ? modifier.fixed_val : 0;
- let modifierRoll = undefined;
- let scaledRoll = undefined;
- let scaledModifier = undefined;
- let missSave = (toHit) ? modifier.miss_mod : modifier.save_fail_mod; //what happens on miss/failed save
- const special = (!modifier.special || modifier.length === 0) ? undefined : (modifier.special && Array.isArray(modifier.special)) ? modifier.special : [modifier.special];
+ for (const roll of action.rolls) {
+ // Create editable Roll object with important props from roll object via anonymous function
+ let editableRoll = (({ damage_type, dice_count, dice_type, fixed_val }) => ({
+ damage_type,
+ dice_count,
+ dice_type,
+ fixed_val,
+ }))(roll);
+ const missSave = toHit ? roll.miss_mod : roll.save_fail_mod; //what happens on miss/failed save
+ const special =
+ !roll.special || roll.length === 0
+ ? undefined
+ : roll.special && Array.isArray(roll.special)
+ ? roll.special
+ : [roll.special];
+ let magical = !!roll.magical;
+
+ // Check for options
+ if (ability.options && config.option && roll.options && roll.options[config.option]) {
+ const option = roll.options[config.option];
+ if (option.ignore) {
+ editableRoll = {};
+ } else {
+ editableRoll.damage_type = option.damage_type || editableRoll.damage_type;
+ editableRoll.dice_type = option.dice_type || editableRoll.dice_type;
+ editableRoll.dice_count = option.dice_count || editableRoll.dice_count;
+ editableRoll.fixed_val = option.fixed_val || editableRoll.fixed_val;
+ magical = option.magical;
+ }
+ }
// Check for versatile. 1 is the alternative option
// Changes only have to be made if the versatile roll is the alternative (1)
- if(config.versatile === 1) {
- damage_type = (modifier.versatile_damage_type) ? modifier.versatile_damage_type : damage_type;
- dice_type = (modifier.versatile_dice_type) ? modifier.versatile_dice_type : dice_type;
- dice_count = (modifier.versatile_dice_count) ? modifier.versatile_dice_count : dice_count;
- fixed_val = (modifier.versatile_fixed_val) ? modifier.versatile_fixed_val : fixed_val;
+ else if (ability.versatile && config.option === 1) {
+ editableRoll.damage_type = roll.versatile_damage_type || editableRoll.damage_type;
+ editableRoll.dice_type = roll.versatile_dice_type || editableRoll.dice_type;
+ editableRoll.dice_count = roll.versatile_dice_count || editableRoll.dice_count;
+ editableRoll.fixed_val = roll.versatile_fixed_val || editableRoll.fixed_val;
+ magical = roll.versatile_magical;
}
// Check if the action scales with the current roll
- let tiers = modifier.level_tiers;
- if(tiers) {
- scaledModifier = this.__levelScaling__(tiers, config.castLevel, spellLevel, config.casterLevel, scaleType);
-
- // Roll the scaledModifier
- if(scaledModifier) {
- // Double the dice count when it's a crit and crit settings are set to roll twice
- if(crit && !this.critSettings) scaledModifier.dice_count = scaledModifier.dice_count*2;
- if(scaledModifier.dice_type && scaledModifier.dice_count) scaledRoll = this.rollD(e.e, scaledModifier.dice_type, scaledModifier.dice_count, scaledModifier.fixed_val);
- // When there is nothing to roll, but only a fixed value
- // still a roll must be created
- else scaledRoll = {
- title: ability.name,
- roll: fixed_val,
- mod: fixed_val,
- throws: [],
- throwsTotal: 0,
- total: parseInt(fixed_val)
- }
- }
+ const tiers = roll.scaling;
+ if (roll.scaling) {
+ editableRoll = this.__levelScaling__(tiers, editableRoll, ability, config);
}
-
- // Roll the modifier
- // If the modifier scales with character level, overwrite the modifierRoll with the scaledRoll
- if(scaleType === 'character_level' && scaledModifier) {
- modifierRoll = scaledRoll;
- scaledRoll = undefined; //Only return the scaled modifierRoll
- } else {
- if(dice_type && dice_count) modifierRoll = this.rollD(e.e, dice_type, dice_count, fixed_val, `${ability.name}`);
+
+ // check crits
+ editableRoll.dice_count = this.getDiceCount(crit, editableRoll.dice_count);
+
+ // Roll the roll
+ let rollResult = undefined;
+ if (editableRoll.dice_type && editableRoll.dice_count) {
+ rollResult = this.rollD(
+ e.e,
+ editableRoll.dice_type,
+ editableRoll.dice_count,
+ editableRoll.fixed_val,
+ `${editableRoll.name}`
+ );
// When there is nothing to roll, but only a fixed value
// still a roll must be created
- else modifierRoll = {
- title: ability.name,
- roll: fixed_val,
- mod: fixed_val,
+ } else {
+ rollResult = {
+ title: editableRoll.name,
+ roll: editableRoll.fixed_val,
+ mod: editableRoll.fixed_val,
throws: [],
throwsTotal: 0,
- total: parseInt(fixed_val)
- }
+ total: parseInt(editableRoll.fixed_val),
+ };
}
- // Double the rolled damage (without the modifier [trhowsTotal])
- // simply add [trhowsTotal] once more to the [total]
- // Only when it's a crit and crit settings are set to doulbe
- if(crit && this.critSettings) {
- modifierRoll.total = modifierRoll.total + modifierRoll.throwsTotal;
+ // Double the rolled damage (without the modifier [throwsTotal])
+ // simply add [throwsTotal] once more to the [total]
+ // Only when it's a crit and crit settings are set to double
+ if (crit && this.critSettings === "double") {
+ rollResult.total = rollResult.total + rollResult.throwsTotal;
+ const { n, d, m, s } = rollResult;
+ rollResult.roll = m !== 0 ? `2x(${n}d${d})${s}${m}` : `2x(${n}d${d})`;
+ }
+ // Add the max damage output of the roll to the total when crit and setting is max
+ if (crit && this.critSettings === "max") {
+ const { n, d, m, s } = rollResult;
+ rollResult.total = rollResult.total + n * d;
+ rollResult.roll = m !== 0 ? `(${n}d${d}+${n * d})${s}${m}` : `(${n}d${d}+${n * d}) `;
}
// Push the rolled modifier to the array with all rolled modifiers
returnRoll.actions[i].rolls.push({
- modifierRoll,
- damage_type,
- scaledRoll,
+ rollResult,
+ damage_type: editableRoll.damage_type,
missSave,
- special
+ magical,
+ special,
});
}
i++;
}
return returnRoll;
},
- __levelScaling__(tiers, castLevel, spellLevel, casterLevel, scaleType) {
- let scaledModifier = undefined;
-
+ __levelScaling__(tiers, roll, ability, config) {
// SPELL SCALE
- if(scaleType === 'spell_scale') {
- let scale = tiers[0].level;
- let dice_type = tiers[0].dice_type;
- let dice_count = tiers[0].dice_count;
- let fixed_val = tiers[0].fixed_val;
+ if (ability.scaling === "spell_scale") {
+ const scale = tiers[0].level;
+ const dice_count = tiers[0].dice_count;
+ const fixed_val = tiers[0].fixed_val;
// Calculate the increase based on spell level, on what level the spell is cast and the scale
- let increase = parseInt(Math.floor((castLevel - spellLevel) / scale));
-
- // If there is an increase,
- if(increase) {
- scaledModifier = {};
- scaledModifier.dice_count = increase * dice_count;
- scaledModifier.dice_type = dice_type;
-
- // Check if there is a fixed value
- // If there is one, add it for every scale level
- // If the spell scales with 1 and is cast 3 levels higer, multiply the fixed value by 3
- scaledModifier.fixed_val = (fixed_val) ? increase * fixed_val : 0;
+ const increase = parseInt(Math.floor((config.cast_level - ability.level) / scale));
+
+ // If there is an increase,
+ if (increase) {
+ roll.dice_count += increase * dice_count;
+ if (roll.fixed_val) roll.fixed_val += fixed_val ? increase * fixed_val : 0;
}
}
// CHARACTER LEVEL
- if(scaleType === 'character_level') {
- tiers.sort((a, b) => (parseInt(a.level) > parseInt(b.level)) ? 1 : -1)
-
- for(let tier of tiers) {
- if(parseInt(casterLevel) >= parseInt(tier.level)) {
- scaledModifier = {
- dice_count: tier.dice_count,
- dice_type: tier.dice_type,
- };
- if(tier.fixed_val) {
- scaledModifier.fixed_val = tier.fixed_val;
- }
+ else if (ability.scaling === "character_level") {
+ tiers.sort((a, b) => (parseInt(a.level) > parseInt(b.level) ? 1 : -1));
+
+ for (let tier of tiers) {
+ if (parseInt(config.caster_level) >= parseInt(tier.level)) {
+ roll.dice_count = tier.dice_count;
+ roll.fixed_val = tier.fixed_val || 0;
}
}
}
- return scaledModifier;
+ return roll;
+ },
+ getAbilityName(ability, config) {
+ if (config.cast_level) {
+ return `${ability.name} (${config.cast_level.toOrdinal()})`;
+ }
+ return ability.name;
},
animateValue(id, start, end, duration) {
if (start === end) return;
const range = end - start;
let current = start;
- const increment = end > start? 1 : -1;
+ const increment = end > start ? 1 : -1;
const stepTime = Math.abs(Math.floor(duration / range));
const obj = document.getElementById(id);
- const timer = setInterval(function() {
- current += increment;
- obj.innerHTML = current;
- if (current == end) {
- clearInterval(timer);
- }
+ const timer = setInterval(function () {
+ current += increment;
+ obj.innerHTML = current;
+ if (current == end) {
+ clearInterval(timer);
+ }
}, stepTime);
},
calcAverage(value, amount = 1) {
- return Math.ceil(((value + 1)/2)*amount);
- }
- }
-}
+ return Math.ceil(((value + 1) / 2) * amount);
+ },
+ getDiceCount(crit, dice_count) {
+ // Crit and setting is roll damage twice
+ if (crit && this.critSettings === undefined) {
+ return dice_count * 2;
+ }
+
+ return dice_count;
+ },
+ },
+};
diff --git a/src/mixins/monster.js b/src/mixins/monster.js
index 3132483de..903e10242 100644
--- a/src/mixins/monster.js
+++ b/src/mixins/monster.js
@@ -7,55 +7,44 @@ export const monsterMixin = {
return {
abilities: abilities,
monster_challenge_rating: {
- 0: { proficiency: 2, xp: 10 },
- 0.125: { proficiency: 2, xp: 25 },
- 0.25: { proficiency: 2, xp: 50 },
- 0.5: { proficiency: 2, xp: 100 },
- 1: { proficiency: 2, xp: 200 },
- 2: { proficiency: 2, xp: 450 },
- 3: { proficiency: 2, xp: 700 },
- 4: { proficiency: 2, xp: 1100 },
- 5: { proficiency: 3, xp: 1800 },
- 6: { proficiency: 3, xp: 2300 },
- 7: { proficiency: 3, xp: 2900 },
- 8: { proficiency: 3, xp: 3900 },
- 9: { proficiency: 4, xp: 5000 },
- 10: { proficiency: 4, xp: 5900 },
- 11: { proficiency: 4, xp: 7200 },
- 12: { proficiency: 4, xp: 8400 },
- 13: { proficiency: 5, xp: 10000 },
- 14: { proficiency: 5, xp: 11500 },
- 15: { proficiency: 5, xp: 13000 },
- 16: { proficiency: 5, xp: 15000 },
- 17: { proficiency: 6, xp: 18000 },
- 18: { proficiency: 6, xp: 20000 },
- 19: { proficiency: 6, xp: 22000 },
- 20: { proficiency: 6, xp: 25000 },
- 21: { proficiency: 7, xp: 33000 },
- 22: { proficiency: 7, xp: 41000 },
- 23: { proficiency: 7, xp: 50000 },
- 24: { proficiency: 7, xp: 62000 },
- 25: { proficiency: 7, xp: 75000 },
- 26: { proficiency: 7, xp: 90000 },
- 27: { proficiency: 7, xp: 105000 },
- 28: { proficiency: 7, xp: 120000 },
- 29: { proficiency: 7, xp: 135000 },
- 30: { proficiency: 9, xp: 155000 }
+ 0: { proficiency: 2, xp: 10 },
+ 0.125: { proficiency: 2, xp: 25 },
+ 0.25: { proficiency: 2, xp: 50 },
+ 0.5: { proficiency: 2, xp: 100 },
+ 1: { proficiency: 2, xp: 200 },
+ 2: { proficiency: 2, xp: 450 },
+ 3: { proficiency: 2, xp: 700 },
+ 4: { proficiency: 2, xp: 1100 },
+ 5: { proficiency: 3, xp: 1800 },
+ 6: { proficiency: 3, xp: 2300 },
+ 7: { proficiency: 3, xp: 2900 },
+ 8: { proficiency: 3, xp: 3900 },
+ 9: { proficiency: 4, xp: 5000 },
+ 10: { proficiency: 4, xp: 5900 },
+ 11: { proficiency: 4, xp: 7200 },
+ 12: { proficiency: 4, xp: 8400 },
+ 13: { proficiency: 5, xp: 10000 },
+ 14: { proficiency: 5, xp: 11500 },
+ 15: { proficiency: 5, xp: 13000 },
+ 16: { proficiency: 5, xp: 15000 },
+ 17: { proficiency: 6, xp: 18000 },
+ 18: { proficiency: 6, xp: 20000 },
+ 19: { proficiency: 6, xp: 22000 },
+ 20: { proficiency: 6, xp: 25000 },
+ 21: { proficiency: 7, xp: 33000 },
+ 22: { proficiency: 7, xp: 41000 },
+ 23: { proficiency: 7, xp: 50000 },
+ 24: { proficiency: 7, xp: 62000 },
+ 25: { proficiency: 7, xp: 75000 },
+ 26: { proficiency: 7, xp: 90000 },
+ 27: { proficiency: 7, xp: 105000 },
+ 28: { proficiency: 7, xp: 120000 },
+ 29: { proficiency: 7, xp: 135000 },
+ 30: { proficiency: 9, xp: 155000 },
},
- monster_sizes: [
- "Tiny",
- "Small",
- "Medium",
- "Large",
- "Huge",
- "Gargantuan"
- ],
+ monster_sizes: ["Tiny", "Small", "Medium", "Large", "Huge", "Gargantuan"],
monster_subtypes: {
- Fiend: [
- "Demon",
- "Devil",
- "Shapechanger",
- ],
+ Fiend: ["Demon", "Devil", "Shapechanger"],
Humanoid: [
"Any race",
"Dwarf",
@@ -70,15 +59,10 @@ export const monsterMixin = {
"Lizardfolk",
"Merfolk",
"Orc",
- "Sahuagin"
- ],
- Monstrosity: [
- "Shapechanger",
- "Titan"
+ "Sahuagin",
],
- Undead: [
- "Shapechanger"
- ]
+ Monstrosity: ["Shapechanger", "Titan"],
+ Undead: ["Shapechanger"],
},
monster_types: [
"Aberration",
@@ -95,7 +79,7 @@ export const monsterMixin = {
"Ooze",
"Plant",
"Swarm of tiny beasts",
- "Undead"
+ "Undead",
],
monster_alignment: [
"Any",
@@ -110,12 +94,7 @@ export const monsterMixin = {
"Neutral evil",
"Chaotic evil",
],
- monster_senses: [
- "blindsight",
- "darkvision",
- "tremorsense",
- "truesight"
- ],
+ monster_senses: ["blindsight", "darkvision", "tremorsense", "truesight"],
conditions: [
"blinded",
"charmed",
@@ -131,11 +110,18 @@ export const monsterMixin = {
"prone",
"restrained",
"stunned",
- "unconscious"
+ "unconscious",
],
- }
+ };
},
methods: {
+ defensesDisplay(defenses) {
+ defenses.forEach((defense, i) => {
+ defense = defense.split("_");
+ defenses[i] = defense.join(" ").capitalizeEach();
+ });
+ return defenses;
+ },
/**
* Parse an old monster to the new format
* @param {object} monster The old monster object
@@ -143,70 +129,73 @@ export const monsterMixin = {
*/
parseMonster(monster, uid, key) {
let new_monster = {};
- new_monster.name = (monster.name) ? monster.name.toLowerCase() : "monster name"; // required
- new_monster.challenge_rating = (Number(monster.challenge_rating) && !isNaN(monster.challenge_rating)) ? monster.challenge_rating : 1; // required
- if(monster.size) new_monster.size = monster.size.toLowerCase().capitalize(); // required
- if(monster.type) new_monster.type = monster.type.toLowerCase().capitalize(); // required
- if(monster.subtype) new_monster.subtype = monster.subtype.toLowerCase().capitalize();
- if(monster.alignment) new_monster.alignment = monster.alignment.toLowerCase().capitalize();
- if(monster.avatar) new_monster.avatar = monster.avatar;
-
- new_monster.armor_class = (monster.ac) ? parseInt(monster.ac) : 1; // required
+ new_monster.name = monster.name ? monster.name.toLowerCase() : "monster name"; // required
+ new_monster.challenge_rating =
+ Number(monster.challenge_rating) && !isNaN(monster.challenge_rating)
+ ? monster.challenge_rating
+ : 1; // required
+ if (monster.size) new_monster.size = monster.size.toLowerCase().capitalize(); // required
+ if (monster.type) new_monster.type = monster.type.toLowerCase().capitalize(); // required
+ if (monster.subtype) new_monster.subtype = monster.subtype.toLowerCase().capitalize();
+ if (monster.alignment) new_monster.alignment = monster.alignment.toLowerCase().capitalize();
+ if (monster.avatar) new_monster.avatar = monster.avatar;
+
+ new_monster.armor_class = monster.ac ? parseInt(monster.ac) : 1; // required
new_monster.armor_class = Math.min(new_monster.armor_class, 99); // limit AC to max 99
new_monster.armor_class = Math.max(new_monster.armor_class, 1); // limit AC to min 1
- new_monster.hit_points = (monster.maxHp) ? parseInt(monster.maxHp) : 1; // required
+ new_monster.hit_points = monster.maxHp ? parseInt(monster.maxHp) : 1; // required
new_monster.hit_points = Math.min(new_monster.hit_points, 9999); // limit HP to max 9999
new_monster.hit_points = Math.max(new_monster.hit_points, 1); // limit HP to min 1
-
- if(monster.hit_dice) new_monster.hit_dice = monster.hit_dice;
- if(monster.friendly) new_monster.friendly = true;
-
- let proficiency = 0
+
+ if (monster.hit_dice) new_monster.hit_dice = monster.hit_dice;
+ if (monster.friendly) new_monster.friendly = true;
+
+ let proficiency = 0;
if (this.monster_challenge_rating[new_monster.challenge_rating].proficiency) {
- proficiency = this.monster_challenge_rating[new_monster.challenge_rating].proficiency
+ proficiency = this.monster_challenge_rating[new_monster.challenge_rating].proficiency;
}
- if(!new_monster.size || !this.monster_sizes.includes(new_monster.size)) {
+ if (!new_monster.size || !this.monster_sizes.includes(new_monster.size)) {
new_monster.size = "Medium";
}
- if(!new_monster.type || !this.monster_types.includes(new_monster.type)) {
+ if (!new_monster.type || !this.monster_types.includes(new_monster.type)) {
new_monster.type = "Beast";
}
// Abilities & Saving Throws
new_monster.saving_throws = [];
- for(const ability of this.abilities) {
+ for (const ability of this.abilities) {
new_monster[ability] = monster[ability] || 10;
- new_monster[ability] = Math.min(new_monster[ability], 99)
- new_monster[ability] = Math.max(new_monster[ability], 0)
+ new_monster[ability] = Math.min(new_monster[ability], 99);
+ new_monster[ability] = Math.max(new_monster[ability], 0);
- if(monster[`${ability}_save`]) {
+ if (monster[`${ability}_save`]) {
new_monster.saving_throws.push(ability);
}
}
- if(new_monster.saving_throws.length === 0) delete new_monster.saving_throws;
+ if (new_monster.saving_throws.length === 0) delete new_monster.saving_throws;
// Skills
new_monster.skills = [];
new_monster.skills_expertise = [];
- for(const skill of Object.values(skills)) {
+ for (const skill of Object.values(skills)) {
const modifier = monster[skill.value];
// Save proficiency
- if(modifier) {
+ if (modifier) {
new_monster.skills.push(skill.value);
// Check for expertise
// If the modifier in old_monster is higher than the ability_mod + proficiency
// that means there is expertise
- if(modifier > proficiency + this.calcMod(new_monster[skill.ability])) {
- new_monster.skills_expertise.push(skill.value)
+ if (modifier > proficiency + this.calcMod(new_monster[skill.ability])) {
+ new_monster.skills_expertise.push(skill.value);
}
}
}
- if(new_monster.skills.length === 0) delete new_monster.skills;
- if(new_monster.skills_expertise.length === 0) delete new_monster.skills_expertise;
+ if (new_monster.skills.length === 0) delete new_monster.skills;
+ if (new_monster.skills_expertise.length === 0) delete new_monster.skills_expertise;
// Speed
if (monster.speed) {
@@ -228,35 +217,32 @@ export const monsterMixin = {
}
if (!found) new_monster.walk_speed = value;
}
- }
- else {
+ } else {
new_monster.walk_speed = 0;
}
// Languages
- const languages = (monster.languages) ? monster.languages.split(",") : null;
-
- if(languages) {
+ const languages = monster.languages ? monster.languages.split(",") : null;
+
+ if (languages) {
new_monster.languages = [];
- for(const language of languages) {
- new_monster.languages.push(
- language.toLowerCase().trim().capitalize()
- );
+ for (const language of languages) {
+ new_monster.languages.push(language.toLowerCase().trim().capitalize());
}
}
// Senses
- const senses = (monster.senses) ? monster.senses.split(",") : null;
- if(senses) {
+ const senses = monster.senses ? monster.senses.split(",") : null;
+ if (senses) {
new_monster.senses = {};
- for(const sense of senses) {
- for(const monster_sense of this.monster_senses) {
- if(sense.trim().includes(monster_sense)) {
+ for (const sense of senses) {
+ for (const monster_sense of this.monster_senses) {
+ if (sense.trim().includes(monster_sense)) {
let new_sense = {};
new_sense[monster_sense] = true;
-
- if(sense.match(/([0-9])+/g)) {
- new_sense.range = parseInt(sense.match(/([0-9])+/g)[0]);
+
+ if (sense.match(/(\d)+/g)) {
+ new_sense.range = parseInt(sense.match(/(\d)+/g)[0]);
}
new_monster.senses[monster_sense] = new_sense;
}
@@ -269,36 +255,36 @@ export const monsterMixin = {
damage_resistances: monster.damage_resistances,
damage_vulnerabilities: monster.damage_vulnerabilities,
damage_immunities: monster.damage_immunities,
- }
+ };
- const resistances = [
- "damage_resistances",
- "damage_vulnerabilities",
- "damage_immunities",
- ];
+ const resistances = ["damage_resistances", "damage_vulnerabilities", "damage_immunities"];
- for(const resistance_type of resistances) {
+ for (const resistance_type of resistances) {
new_monster[resistance_type] = [];
- if(defenses[resistance_type]) {
- for(const type of damage_types) {
- if(defenses[resistance_type].toLowerCase().search(type) > -1 && !new_monster[resistance_type].includes(type)) {
+ if (defenses[resistance_type]) {
+ for (const type of damage_types) {
+ if (
+ defenses[resistance_type].toLowerCase().search(type) > -1 &&
+ !new_monster[resistance_type].includes(type)
+ ) {
new_monster[resistance_type].push(type);
}
}
}
- if(new_monster[resistance_type].length === 0) delete new_monster[resistance_type];
+ if (new_monster[resistance_type].length === 0) delete new_monster[resistance_type];
}
- const condition_immunities = (monster.condition_immunities) ? monster.condition_immunities.split(",") : null;
-
+ const condition_immunities = monster.condition_immunities
+ ? monster.condition_immunities.split(",")
+ : null;
- if(condition_immunities) {
+ if (condition_immunities) {
new_monster.condition_immunities = [];
- for(let immunity of condition_immunities) {
- if(immunity) {
+ for (let immunity of condition_immunities) {
+ if (immunity) {
const trimmed_immunity = immunity.trim().toLowerCase();
- if(this.conditions.includes(trimmed_immunity)) {
+ if (this.conditions.includes(trimmed_immunity)) {
new_monster.condition_immunities.push(trimmed_immunity);
}
}
@@ -306,11 +292,16 @@ export const monsterMixin = {
}
// Abilities
- for(const action_type of ["special_abilities", "actions", "legendary_actions", "reactions"]) {
- if(monster[action_type]) {
+ for (const action_type of [
+ "special_abilities",
+ "actions",
+ "legendary_actions",
+ "reactions",
+ ]) {
+ if (monster[action_type]) {
new_monster[action_type] = [];
- for(const ability of monster[action_type]) {
+ for (const ability of monster[action_type]) {
// Store a list of actions in the list
// We will use only 1 action now, for damage or healing
// But later we might want to add conditions and reminders
@@ -324,50 +315,59 @@ export const monsterMixin = {
action_list: [
{
type: "other",
- }
- ]
+ },
+ ],
};
let fail_miss = "";
- if(action_type === "legendary_actions") newAbility.legendary_cost = 1;
+ if (action_type === "legendary_actions") newAbility.legendary_cost = 1;
// Find recharge, limit and legendary cost
- if(ability.name && ability.name.match(/\((.*?)\)/g)) {
+ if (ability.name && ability.name.match(/\((.*?)\)/g)) {
const type = ability.name.match(/\((.*?)\)/g)[0];
-
- if(type.toLowerCase().includes("recharge")){
- if(type.match(/[0-9]+(-[0-9]+)*/)) {
- newAbility.recharge = type.match(/[0-9]+(-[0-9]+)*/)[0];
+
+ if (type.toLowerCase().includes("recharge")) {
+ if (type.match(/\d+(-\d+)*/)) {
+ newAbility.recharge = type.match(/\d+(-\d+)*/)[0];
} else {
newAbility.recharge = "rest";
- }
+ }
}
- if(type.toLowerCase().includes("day")){
- newAbility.limit = type.match(/([0-9])+/g) ? type.match(/([0-9])+/g)[0] : 1;
+ if (type.toLowerCase().includes("day")) {
+ newAbility.limit = type.match(/(\d)+/g) ? type.match(/(\d)+/g)[0] : 1;
newAbility.limit_type = "day";
}
- if(type.toLowerCase().includes("turn")){
- newAbility.limit = type.match(/([0-9])+/g) ? type.match(/([0-9])+/g)[0] : 1;
+ if (type.toLowerCase().includes("turn")) {
+ newAbility.limit = type.match(/(\d)+/g) ? type.match(/(\d)+/g)[0] : 1;
newAbility.limit_type = "turn";
}
- if(action_type === "legendary_actions" && type.toLowerCase().includes("costs")){
- newAbility.legendary_cost = type.match(/([0-9])+/g) ? type.match(/([0-9])+/g)[0] : 1;
+ if (action_type === "legendary_actions" && type.toLowerCase().includes("costs")) {
+ newAbility.legendary_cost = type.match(/(\d)+/g) ? type.match(/(\d)+/g)[0] : 1;
}
newAbility.name = newAbility.name.replace(type, "").trim();
}
- if(ability.damage_dice || (ability.desc && ability.desc.toLowerCase().match(/(saving throw)/g))) {
+ if (
+ ability.damage_dice ||
+ (ability.desc && ability.desc.toLowerCase().match(/(saving throw)/g))
+ ) {
// Find the range
- const reach = (ability.desc) ? ability.desc.toLowerCase().match(/reach\s?([0-9]+(?:\/[0-9]+)?)/) : null;
- const range = (ability.desc) ? ability.desc.toLowerCase().match(/range\s?([0-9]+(?:\/[0-9]+)?)/) : null;
+ const reach = ability.desc
+ ? ability.desc.toLowerCase().match(/reach\s?(\d+(?:\/\d+)?)/)
+ : null;
+ const range = ability.desc
+ ? ability.desc.toLowerCase().match(/range\s?(\d+(?:\/\d+)?)/)
+ : null;
- if(reach) newAbility.reach = parseInt(reach[1]);
- if(range) newAbility.range = range[1];
+ if (reach) newAbility.reach = parseInt(reach[1]);
+ if (range) newAbility.range = range[1];
// Check if it's a targeted action or saving throw
- if(ability.attack_bonus && ability.attack_bonus !== 0) {
- if(ability.desc && ability.desc.toLowerCase().match(/(melee weapon)/g)) newAbility.action_list[0].type = "melee_weapon";
- else if(ability.desc && ability.desc.toLowerCase().match(/(ranged weapon)/g)) newAbility.action_list[0].type = "ranged_weapon";
+ if (ability.attack_bonus && ability.attack_bonus !== 0) {
+ if (ability.desc && ability.desc.toLowerCase().match(/(melee weapon)/g))
+ newAbility.action_list[0].type = "melee_weapon";
+ else if (ability.desc && ability.desc.toLowerCase().match(/(ranged weapon)/g))
+ newAbility.action_list[0].type = "ranged_weapon";
else newAbility.action_list[0].type = "melee_weapon";
newAbility.action_list[0].attack_bonus = ability.attack_bonus;
@@ -376,14 +376,14 @@ export const monsterMixin = {
newAbility.action_list[0].type = "save";
fail_miss = "save_fail_mod";
- const save_dc = (ability.desc) ? ability.desc.match(/DC\s?([0-9]+)/) : null;
- if(save_dc) {
+ const save_dc = ability.desc ? ability.desc.match(/DC\s?(\d+)/) : null;
+ if (save_dc) {
newAbility.action_list[0].save_dc = parseInt(save_dc[1]);
}
// Find the ability
- for(const ab of this.abilities) {
- if(ability.desc && ability.desc.toLowerCase().search(ab) > -1) {
+ for (const ab of this.abilities) {
+ if (ability.desc && ability.desc.toLowerCase().search(ab) > -1) {
newAbility.action_list[0].save_ability = ab;
}
}
@@ -391,11 +391,11 @@ export const monsterMixin = {
// Create an array of damage types found in the description
let types = [];
- for(const type of damage_types) {
- const position = (ability.desc) ? ability.desc.toLowerCase().search(type) : -1;
- if(position > -1 && !types.includes(type)) {
+ for (const type of damage_types) {
+ const position = ability.desc ? ability.desc.toLowerCase().search(type) : -1;
+ if (position > -1 && !types.includes(type)) {
// Make sure they're in the correct order
- if(types[0] && position > ability.desc.toLowerCase().search(types[0])) {
+ if (types[0] && position > ability.desc.toLowerCase().search(types[0])) {
types.push(type);
} else {
types.unshift(type);
@@ -405,12 +405,11 @@ export const monsterMixin = {
newAbility.action_list[0].rolls = [];
let actually_fixed = null;
- if(ability.damage_dice) {
+ if (ability.damage_dice) {
ability.damage_dice.split("+").forEach((damage, index) => {
-
- const damage_type = (types[index]) ? types[index] : "slashing";
+ const damage_type = types[index] ? types[index] : "slashing";
let newRoll = {
- damage_type
+ damage_type,
};
const input = damage.split("d");
@@ -418,20 +417,20 @@ export const monsterMixin = {
newRoll.dice_count = parseInt(input[0]);
newRoll.dice_type = parseInt(input[1]);
if (isNaN(newRoll.dice_count)) {
- console.log(damage)
+ console.log(damage);
}
} else {
actually_fixed = parseInt(input[0]) || null;
}
-
- newRoll[fail_miss] = (fail_miss === "miss_mod") ? 0 : 0.5;
+
+ newRoll[fail_miss] = fail_miss === "miss_mod" ? 0 : 0.5;
newAbility.action_list[0].rolls.push(newRoll);
});
// Check if there is a damage bonus
// Add it only once (to the first roll by default, this might be wrong in some cases)
- if(ability.damage_bonus && newAbility.action_list[0].rolls.length > 0) {
+ if (ability.damage_bonus && newAbility.action_list[0].rolls.length > 0) {
newAbility.action_list[0].rolls[0].fixed_val = parseInt(ability.damage_bonus);
} else if (actually_fixed) {
newAbility.action_list[0].rolls[0].fixed_val = parseInt(actually_fixed);
@@ -443,10 +442,10 @@ export const monsterMixin = {
}
}
// Legendary action count
- if(new_monster.legendary_actions && new_monster.legendary_actions.length > 0) {
+ if (new_monster.legendary_actions && new_monster.legendary_actions.length > 0) {
new_monster.legendary_count = 3;
}
return new_monster;
- }
- }
-}
+ },
+ },
+};
diff --git a/src/mixins/runEncounter.js b/src/mixins/runEncounter.js
index d70da8922..0812a8249 100644
--- a/src/mixins/runEncounter.js
+++ b/src/mixins/runEncounter.js
@@ -2,111 +2,117 @@ import { dice } from "src/mixins/dice.js";
import { mapActions, mapGetters } from "vuex";
export const runEncounter = {
- mixins: [dice],
- computed: {
- ...mapGetters([
- "entities",
- "broadcast"
- ]),
- _share() {
- return (this.broadcast.shares && this.broadcast.shares.includes("action_rolls")) || false;
- },
- },
- methods: {
- ...mapActions([
- "setActionRoll",
- "set_limitedUses"
- ]),
- ...mapActions("campaigns", ["set_share"]),
- roll_action({e, action_index, action, category, entity, targets, versatile=undefined }) {
- let roll;
- const config = {
- type: "monster_action",
- versatile
- }
+ mixins: [dice],
+ computed: {
+ ...mapGetters(["entities", "broadcast"]),
+ _share() {
+ return (this.broadcast.shares && this.broadcast.shares.includes("action_rolls")) || false;
+ },
+ },
+ methods: {
+ ...mapActions(["setActionRoll", "set_limitedUses"]),
+ ...mapActions("campaigns", ["set_share"]),
+ roll_action({
+ e,
+ action_index,
+ action,
+ category,
+ entity,
+ targets,
+ projectiles = 1,
+ option = undefined,
+ }) {
+ let roll;
+ const config = {
+ type: "monster_action",
+ option,
+ };
- // Roll once for AOE
- if(action.aoe_type) {
- roll = this.rollAction(e, action, config);
- if(this._share) this.shareRoll(roll, entity, targets);
- }
+ // Roll once for AOE
+ if (action.aoe_type) {
+ roll = this.rollAction(e, action, config);
+ if (this._share) this.shareRoll(roll, entity, targets);
+ }
- // Check for limited uses
- if(action.limit || action.recharge) {
- this.set_limitedUses({
- key: entity.key,
- index: action_index,
- category
- });
- }
- if(action.legendary_cost) {
- this.set_limitedUses({
- key: entity.key,
- index: "legendaries_used",
- category,
- cost: action.legendary_cost
- });
- }
+ // Check for limited uses
+ if (action.limit || action.recharge) {
+ this.set_limitedUses({
+ key: entity.key,
+ index: action_index,
+ category,
+ });
+ }
+ if (action.legendary_cost) {
+ this.set_limitedUses({
+ key: entity.key,
+ index: "legendaries_used",
+ category,
+ cost: action.legendary_cost,
+ });
+ }
- for(const key of targets) {
- let newRoll = { ...roll };
+ for (const key of targets) {
+ let newRoll = { ...roll };
- // Reroll for each target if it's not AOE
- if(!action.aoe_type) {
- newRoll = this.rollAction(e, action, config);
- if(this._share) this.shareRoll(newRoll, entity, [key]);
- }
+ // Reroll for each target if it's not AOE
+ if (!action.aoe_type) {
+ newRoll = this.rollAction(e, action, config);
+ if (this._share) this.shareRoll(newRoll, entity, [key]);
+ }
- // Set the target and current
- this.$set(newRoll, "target", this.entities[key]);
- this.$set(newRoll, "current", entity);
+ // Set the target and current
+ this.$set(newRoll, "target", this.entities[key]);
+ this.$set(newRoll, "current", entity);
- this.setActionRoll(newRoll);
- }
- },
- shareRoll(roll, entity, targets) {
- const key = Date.now() + Math.random().toString(36).substring(4);
- let share = {
- key,
- type: "action_roll",
- entity_key: entity.key,
- encounter_id: this.encounterId,
- notification: {
- title: roll.name,
- targets,
- actions: []
- }
- };
- roll.actions.forEach((action, action_index) => {
- const type = (action.type === "healing") ? "healing" : "damage";
+ this.setActionRoll(newRoll);
+ }
+ },
+ shareRoll(roll, entity, targets) {
+ const key = Date.now() + Math.random().toString(36).substring(4);
+ let share = {
+ key,
+ type: "action_roll",
+ entity_key: entity.key,
+ encounter_id: this.encounterId,
+ notification: {
+ title: roll.name,
+ targets,
+ actions: [],
+ },
+ };
+ roll.actions.forEach((action, action_index) => {
+ const type = action.type === "healing" ? "healing" : "damage";
- share.notification.actions[action_index] = {
- rolls: [],
- type
- };
- // To hit
- if(action.toHit) {
- const toHit = action.toHit;
- share.notification.actions[action_index].toHit = {
- roll: toHit.roll,
- total: toHit.total
- }
- if(toHit.ignored) share.notification.actions[action_index].toHit.advantage_disadvantage = this.advantage(toHit.advantage_disadvantage);
- }
+ share.notification.actions[action_index] = {
+ rolls: [],
+ type,
+ };
+ // To hit
+ if (action.toHit) {
+ const toHit = action.toHit;
+ share.notification.actions[action_index].toHit = {
+ roll: toHit.roll,
+ total: toHit.total,
+ };
+ if (toHit.ignored)
+ share.notification.actions[action_index].toHit.advantage_disadvantage = this.advantage(
+ toHit.advantage_disadvantage
+ );
+ }
- //Rolls
- action.rolls.forEach((item, roll_index) => {
- share.notification.actions[action_index].rolls[roll_index] = {
- damage_type: item.damage_type || null,
- roll: item.modifierRoll.roll,
- total: item.modifierRoll.total,
- };
- });
- });
- this.set_share({ id: this.broadcast.live, share});
- },
- advantage(input) {
- return Object.keys(input)[0].charAt(0);
- }
- }
-}
\ No newline at end of file
+ //Rolls
+ action.rolls.forEach((item, roll_index) => {
+ share.notification.actions[action_index].rolls[roll_index] = {
+ damage_type: item.damage_type || null,
+ roll: item.modifierRoll.roll,
+ total: item.modifierRoll.total,
+ };
+ });
+ });
+ this.set_share({ id: this.broadcast.live, share });
+ },
+ advantage(input) {
+ return Object.keys(input)[0].charAt(0);
+ },
+ },
+};
diff --git a/src/mixins/spells.js b/src/mixins/spells.js
deleted file mode 100644
index 7bf5ce84e..000000000
--- a/src/mixins/spells.js
+++ /dev/null
@@ -1,112 +0,0 @@
-export const spells = {
- data() {
- return {
- spell_levels: [
- {
- value: 0,
- label: "Cantrip"
- },
- {
- value: 1,
- label: "1st"
- },
- {
- value: 2,
- label: "2nd"
- },
- {
- value: 3,
- label: "3rd"
- },
- {
- value: 4,
- label: "4th"
- },
- {
- value: 5,
- label: "5th"
- },
- {
- value: 5,
- label: "6th"
- },
- {
- value: 7,
- label: "7th"
- },
- {
- value: 8,
- label: "8th"
- },
- {
- value: 9,
- label: "9th"
- },
- ],
- spell_schools: [
- { label: "Abjuration", value: "abjuration" },
- { label: "Conjuration", value: "conjuration" },
- { label: "Divination", value: "divination" },
- { label: "Enchantment", value: "enchantment" },
- { label: "Evocation", value: "evocation" },
- { label: "Illusion", value: "illusion" },
- { label: "Necromancy", value: "necromancy" },
- { label: "Transmutation", value: "transmutation" },
- ],
- spell_components: [
- { label: "Verbal", value: "verbal" },
- { label: "Somatic", value: "somatic" },
- { label: "Material", value: "material" }
- ],
- spell_cast_time_types: [
- { label: "Action", value: "action" },
- { label: "Bonus Action", value: "bonus_action" },
- { label: "Reaction", value: "reaction" },
- { label: "Minute", value: "minute" },
- { label: "Hour", value: "hour" },
- { label: "No Action", value: "no_action" },
- { label: "Special", value: "special" },
- ],
- spell_range_types: [
- { label: "Self", value: "self" },
- { label: "Touch", value: "touch" },
- { label: "Ranged", value: "ranged" },
- { label: "Sight", value: "sight" },
- { label: "Unlimited", value: "unlimited" },
- ],
- spell_duration_types: [
- { label: "Concentration", value: "concentration" },
- { label: "Instantaneous", value: "instantaneous" },
- { label: "Special", value: "special" },
- { label: "Time", value: "time" },
- { label: "Until Dispelled", value: "until_dispelled" },
- { label: "Until Dispelled or Triggered", value: "until_dispelled_or_triggered" },
- ],
- spell_duration_types_time: [ "concentration", "time" ],
- spell_duration_times: [
- { label: "Round", value: "round" },
- { label: "Minute", value: "minute" },
- { label: "Hour", value: "hour" },
- { label: "Day", value: "day" },
- ],
- aoe_types: [
- { label: "None", value: "none" },
- { label: "Cone", value: "cone" },
- { label: "Cube", value: "cube" },
- { label: "Cylinder", value: "cylinder" },
- { label: "Line", value: "line" },
- { label: "Sphere", value: "sphere" },
- { label: "Square", value: "square" },
- { label: "Square Feet", value: "square feet" },
- ],
- level_scaling: [
- { label: "None", value: "none" },
- { label: "Character Level", value: "character_level" },
- { label: "Spell Scale", value: "spell_scale" },
- { label: "Spell Level", value: "spell_level" },
- ],
- }
- },
- methods: {
- }
-}
diff --git a/src/router/routes.js b/src/router/routes.js
index 72f683239..ffc6d4c9b 100644
--- a/src/router/routes.js
+++ b/src/router/routes.js
@@ -354,6 +354,48 @@ const routes = [
],
},
+ // Spells
+ {
+ path: "spells",
+ component: {
+ render(c) {
+ return c("router-view");
+ },
+ },
+ meta: {
+ title: "Spells",
+ },
+ children: [
+ {
+ path: "",
+ name: "Spells",
+ component: () => import("src/views/UserContent/Spells/Spells.vue"),
+ meta: {
+ title: "Spells",
+ description: "Your custom spells on Harmless Key.",
+ },
+ },
+ {
+ path: "add-spell",
+ name: "Add spell",
+ component: () => import("src/views/UserContent/Spells/EditSpell.vue"),
+ meta: {
+ title: "Add spell",
+ description: "Create a new spell on Harmless Key.",
+ },
+ },
+ {
+ path: ":id",
+ name: "Edit spell",
+ component: () => import("src/views/UserContent/Spells/EditSpell.vue"),
+ meta: {
+ title: "Edit spell",
+ description: "Edit an existing spell on Harmless Key.",
+ },
+ },
+ ],
+ },
+
// Reminders
{
path: "reminders",
@@ -536,7 +578,7 @@ const routes = [
meta: {
title: "D&D 5e Tools",
description:
- "Online tools for D&D 5e. \nCombat Tracker \nEncounter Builder \nMonster builder \nCharacter Builder \nCompendium",
+ "Online tools for D&D 5e. \nCombat Tracker \nEncounter Builder \nMonster builder \nSpell creator \nCharacter Builder \nCompendium",
},
},
{
@@ -544,7 +586,7 @@ const routes = [
name: "ToolsCombatTracker",
component: () => import("src/views/Tools/CombatTracker"),
meta: {
- title: "Combat Tracker",
+ title: "D&D 5e Combat Tracker",
description: "An advanced initiative tracker for Dungeons and Dragons 5th edition.",
},
},
@@ -556,9 +598,9 @@ const routes = [
},
},
meta: {
- title: "Encounter Builder",
+ title: "D&D 5e Encounter Builder",
description:
- "An encounter builder for Dungeons and Dragons 5th edition. It calculates the difficulty of your encounter and you can directly run it in our Combat Tracker.",
+ "An encounter builder for D&D 5e. It calculates the difficulty of your encounter and you can directly run it in our Combat Tracker.",
},
children: [
{
@@ -566,9 +608,9 @@ const routes = [
name: "ToolsEncounterBuilder",
component: () => import("src/views/Tools/EncounterBuilder"),
meta: {
- title: "Encounter Builder",
+ title: "D&D 5e Encounter Builder",
description:
- "An encounter builder for Dungeons and Dragons 5th edition. It calculates the difficulty of your encounter and you can directly run it in our Combat Tracker.",
+ "An encounter builder for D&D 5e. It calculates the difficulty of your encounter and you can directly run it in our Combat Tracker.",
},
},
{
@@ -576,7 +618,7 @@ const routes = [
name: "ToolsBuildEncounter",
component: () => import("src/views/UserContent/Encounters/Edit"),
meta: {
- title: "Build encounter",
+ title: "Build encounter for D&D 5e",
description:
"Create an encounter for D&D 5e and find out it's difficulty. Once you're finished you can run it in our Combat Tracker.",
side: false,
@@ -592,9 +634,9 @@ const routes = [
},
},
meta: {
- title: "Monster creator",
+ title: "D&D 5e Monster creator",
description:
- "An advanced monster creator for Dungeons and Dragons 5th edition. Create a stat block with easy to roll actions.",
+ "An advanced monster creator for D&D 5e. Create a stat block with easy to roll actions.",
},
children: [
{
@@ -604,7 +646,7 @@ const routes = [
meta: {
title: "Dungeons & Dragons Monster Creator",
description:
- "An advanced monster creator for Dungeons and Dragons 5th edition. Create a stat block with easy to roll actions.",
+ "An advanced monster creator for D&D 5e. Create a stat block with easy to roll actions.",
},
},
{
@@ -618,6 +660,41 @@ const routes = [
},
],
},
+ {
+ path: "spell-creator",
+ component: {
+ render(c) {
+ return c("router-view");
+ },
+ },
+ meta: {
+ title: "D&D 5e Spell creator",
+ description:
+ "Create spells for D&D 5e to roll directly or use in your custom spellcaster monsters.",
+ },
+ children: [
+ {
+ path: "",
+ name: "ToolsSpellCreator",
+ component: () => import("src/views/Tools/SpellCreator"),
+ meta: {
+ title: "Dungeons & Dragons Spell Creator",
+ description:
+ "Create spells for D&D 5e to roll directly or use in your custom spellcaster monsters.",
+ },
+ },
+ {
+ path: "create-spell",
+ name: "ToolsCreateSpell",
+ component: () => import("src/views/UserContent/Spells/EditSpell"),
+ meta: {
+ title: "Create spell",
+ description:
+ "Create your custom D&D 5e spell to roll directly or use in your custom spellcaster monsters.",
+ },
+ },
+ ],
+ },
{
path: "character-builder",
name: "ToolsCharacterBuilder",
@@ -625,7 +702,7 @@ const routes = [
meta: {
title: "Character Builder",
description:
- "An advanced character builder for Dungeons and Dragons 5th edition. Create a character sheet for you character.",
+ "An advanced character builder for D&D 5e. Create a character sheet for you character.",
},
},
],
@@ -1048,7 +1125,7 @@ const routes = [
children: [
{
path: "",
- name: "Spells",
+ name: "ContributeSpells",
component: () => import("src/views/Contribute/Spells.vue"),
},
{
@@ -1069,7 +1146,7 @@ const routes = [
},
{
path: "edit",
- name: "Edit spell",
+ name: "Edit contribute spell",
component: () => import("src/components/contribute/spell/edit.vue"),
},
],
@@ -1165,6 +1242,24 @@ const routes = [
},
],
},
+ {
+ path: "/cookies",
+ component: () => import("src/layouts/default"),
+ meta: {
+ title: "Cookies",
+ },
+ children: [
+ {
+ path: "",
+ name: "Cookies",
+ component: () => import("src/views/Pages/Cookies"),
+ meta: {
+ title: "Cookies",
+ description: "Cookie information for Harmless Key.",
+ },
+ },
+ ],
+ },
{
path: "/sign-in",
name: "signIn",
diff --git a/src/schemas/hk-npc-schema.json b/src/schemas/hk-npc-schema.json
index 99feab8e3..228a7303a 100644
--- a/src/schemas/hk-npc-schema.json
+++ b/src/schemas/hk-npc-schema.json
@@ -9,6 +9,14 @@
"type": "string",
"maxLength": 100
},
+ "created": {
+ "title": "Created",
+ "type": "integer"
+ },
+ "updated": {
+ "title": "Updated",
+ "type": "integer"
+ },
"name": {
"title": "Name",
"type": "string",
@@ -477,9 +485,7 @@
"range": { "$ref": "#/$defs/action-range" },
"aoe_type": { "$ref": "#/$defs/action-aoe-type" },
"aoe_size": { "$ref": "#/$defs/action-aoe-size" },
- "versatile": { "$ref": "#/$defs/action-versatile" },
- "versatile_one": { "$ref": "#/$defs/action-versatile-one" },
- "versatile_two": { "$ref": "#/$defs/action-versatile-two" },
+ "options": { "$ref": "#/$defs/action-options" },
"action_list": { "$ref": "#/$defs/action-list" }
},
"required": ["name"]
@@ -501,9 +507,7 @@
"range": { "$ref": "#/$defs/action-range" },
"aoe_type": { "$ref": "#/$defs/action-aoe-type" },
"aoe_size": { "$ref": "#/$defs/action-aoe-size" },
- "versatile": { "$ref": "#/$defs/action-versatile" },
- "versatile_one": { "$ref": "#/$defs/action-versatile-one" },
- "versatile_two": { "$ref": "#/$defs/action-versatile-two" },
+ "options": { "$ref": "#/$defs/action-options" },
"action_list": { "$ref": "#/$defs/action-list" }
},
"required": ["name"]
@@ -538,9 +542,7 @@
"range": { "$ref": "#/$defs/action-range" },
"aoe_type": { "$ref": "#/$defs/action-aoe-type" },
"aoe_size": { "$ref": "#/$defs/action-aoe-size" },
- "versatile": { "$ref": "#/$defs/action-versatile" },
- "versatile_one": { "$ref": "#/$defs/action-versatile-one" },
- "versatile_two": { "$ref": "#/$defs/action-versatile-two" },
+ "options": { "$ref": "#/$defs/action-options" },
"action_list": { "$ref": "#/$defs/action-list" }
},
"required": ["name", "legendary_cost"]
@@ -562,9 +564,7 @@
"range": { "$ref": "#/$defs/action-range" },
"aoe_type": { "$ref": "#/$defs/action-aoe-type" },
"aoe_size": { "$ref": "#/$defs/action-aoe-size" },
- "versatile": { "$ref": "#/$defs/action-versatile" },
- "versatile_one": { "$ref": "#/$defs/action-versatile-one" },
- "versatile_two": { "$ref": "#/$defs/action-versatile-two" },
+ "options": { "$ref": "#/$defs/action-options" },
"action_list": { "$ref": "#/$defs/action-list" }
},
"required": ["name"]
@@ -677,7 +677,7 @@
"action-aoe-type": {
"title": "AOE type",
"type": "string",
- "enum": ["cone", "cube", "cylinder", "line", "sphere", "square", "square feet"]
+ "enum": ["cone", "cube", "cylinder", "line", "radius", "sphere", "square", "square feet"]
},
"action-aoe-size": {
"title": "AOE size",
@@ -685,19 +685,12 @@
"minimum": 0,
"maximum": 999
},
- "action-versatile": {
- "title": "versatile",
- "type": "boolean"
- },
- "action-versatile-one": {
- "title": "Versatile one",
- "type": "string",
- "maxLength": 25
- },
- "action-versatile-two": {
- "title": "Versatile two",
- "type": "string",
- "maxLength": 25
+ "action-options": {
+ "title": "Options",
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
},
"action-list": {
"title": "Action list",
@@ -762,26 +755,43 @@
"minimum": -99,
"maximum": 99
},
- "versatile_damage_type": {
- "$ref": "#/$defs/damage-type-select"
- },
- "versatile_dice_count": {
- "title": "versatile dice count",
- "type": "integer",
- "minimum": 1,
- "maximum": 99
- },
- "versatile_dice_type": {
- "title": "Versatile dice type",
- "type": "integer",
- "minimum": 1,
- "maximum": 20
- },
- "versatile_fixed_val": {
- "title": "Versatile fixed val",
- "type": "integer",
- "minimum": -99,
- "maximum": 99
+ "options": {
+ "title": "Options",
+ "type": "object",
+ "additionalProperties": true,
+ "properties": {
+ "^[A-Za-z0-9\\._%\\+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "ignore": {
+ "title": "Ignore",
+ "type": "boolean"
+ },
+ "damage_type": {
+ "$ref": "#/$defs/damage-type-select"
+ },
+ "dice_count": {
+ "title": "Dice count",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99
+ },
+ "dice_type": {
+ "title": "Dice type",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20
+ },
+ "fixed_val": {
+ "title": "Fixed val",
+ "type": "integer",
+ "minimum": -99,
+ "maximum": 99
+ }
+ }
+ }
+ }
},
"projectile_count": {
"title": "Projectile count",
diff --git a/src/schemas/hk-spell-schema.json b/src/schemas/hk-spell-schema.json
new file mode 100644
index 000000000..1c94ecc86
--- /dev/null
+++ b/src/schemas/hk-spell-schema.json
@@ -0,0 +1,383 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Harmless Key Spell",
+ "description": "JSON Schema for importing Harmless Key spells",
+ "additionalProperties": false,
+ "type": "object",
+ "properties": {
+ "harmless_key": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "created": {
+ "title": "Created",
+ "type": "integer"
+ },
+ "updated": {
+ "title": "Updated",
+ "type": "integer"
+ },
+ "name": {
+ "title": "Name",
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 100
+ },
+ "source": {
+ "title": "Source",
+ "type": "string",
+ "maxLength": 30
+ },
+ "level": {
+ "title": "Source",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 9
+ },
+ "school": {
+ "type": "string",
+ "enum": [
+ "abjuration",
+ "conjuration",
+ "divination",
+ "enchantment",
+ "evocation",
+ "illusion",
+ "necromancy",
+ "transmutation"
+ ]
+ },
+ "cast_time_type": {
+ "type": "string",
+ "enum": ["action", "bonus_action", "reaction", "minute", "hour", "no_action", "special"]
+ },
+ "cast_time": {
+ "title": "Cast time",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 999
+ },
+ "cast_time_react_desc": {
+ "title": "Cast time reaction description",
+ "type": "string",
+ "maxLength": 200
+ },
+ "components": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["verbal", "somatic", "material"]
+ }
+ },
+ "material_description": {
+ "title": "Material description",
+ "type": "string",
+ "maxLength": 500
+ },
+ "range_type": {
+ "type": "string",
+ "enum": ["self", "touch", "ranged", "sight", "unlimited", "special"]
+ },
+ "range": {
+ "title": "Range",
+ "type": "integer",
+ "minimum": 0,
+ "maximum": 9999999
+ },
+ "classes": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": [
+ "bard",
+ "barbarian",
+ "cleric",
+ "druid",
+ "fighter",
+ "monk",
+ "paladin",
+ "ranger",
+ "rogue",
+ "sorcerer",
+ "warlock",
+ "wizard"
+ ]
+ }
+ },
+ "duration_type": {
+ "type": "string",
+ "enum": [
+ "concentration",
+ "instantaneous",
+ "special",
+ "time",
+ "until_dispelled",
+ "until_dispelled_or_triggered"
+ ]
+ },
+ "duration": {
+ "title": "Range",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 999
+ },
+ "duration_scale": {
+ "type": "string",
+ "enum": ["round", "minute", "hour", "day"]
+ },
+ "aoe_type": {
+ "title": "AOE type",
+ "type": "string",
+ "enum": [
+ "none",
+ "cone",
+ "cube",
+ "cylinder",
+ "line",
+ "radius",
+ "sphere",
+ "square",
+ "square feet"
+ ]
+ },
+ "aoe_size": {
+ "title": "AOE Size",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99999
+ },
+ "ritual": {
+ "type": "boolean"
+ },
+ "scaling": {
+ "title": "Scaling",
+ "type": "string",
+ "enum": ["none", "character_level", "spell_scale", "spell_level"]
+ },
+ "description": {
+ "title": "Description",
+ "type": "string",
+ "maxLength": 5000
+ },
+ "higher_level": {
+ "title": "At higher levels",
+ "type": "string",
+ "maxLength": 1000
+ },
+ "projectiles": {
+ "title": "Projectiles",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99
+ },
+ "projectile_scaling": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "level": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20
+ },
+ "projectile_count": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 25
+ }
+ }
+ }
+ },
+ "options": {
+ "title": "Options",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "maxLength": 30
+ }
+ },
+ "actions": {
+ "title": "Action list",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "name": {
+ "type": "string",
+ "maxLength": 100
+ },
+ "type": {
+ "title": "type",
+ "type": "string",
+ "enum": [
+ "melee_weapon",
+ "ranged_weapon",
+ "spell_attack",
+ "save",
+ "damage",
+ "healing",
+ "other"
+ ]
+ },
+ "save_ability": {
+ "$ref": "#/$defs/ability-select"
+ },
+ "rolls": {
+ "title": "Rolls",
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "damage_type": {
+ "$ref": "#/$defs/damage-type-select"
+ },
+ "dice_count": {
+ "title": "Dice count",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99
+ },
+ "dice_type": {
+ "title": "Dice type",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20
+ },
+ "fixed_val": {
+ "title": "Fixed val",
+ "type": "integer",
+ "minimum": -99,
+ "maximum": 99
+ },
+ "primary": {
+ "title": "Primary stat modifier",
+ "type": "boolean"
+ },
+ "options": {
+ "title": "Options",
+ "type": "object",
+ "additionalProperties": true,
+ "properties": {
+ "^[A-Za-z0-9\\._%\\+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,6}$": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "ignore": {
+ "title": "Ignore",
+ "type": "boolean"
+ },
+ "damage_type": {
+ "$ref": "#/$defs/damage-type-select"
+ },
+ "dice_count": {
+ "title": "Dice count",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99
+ },
+ "dice_type": {
+ "title": "Dice type",
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20
+ },
+ "fixed_val": {
+ "title": "Fixed val",
+ "type": "integer",
+ "minimum": -99,
+ "maximum": 99
+ }
+ }
+ }
+ }
+ },
+ "miss_mod": {
+ "title": "Miss modifier",
+ "type": "number",
+ "minimum": 0,
+ "maximum": 2
+ },
+ "save_fail_mod": {
+ "title": "Save fail modifier",
+ "type": "number",
+ "minimum": 0,
+ "maximum": 2
+ },
+ "special": {
+ "title": "special",
+ "type": "array",
+ "items": {
+ "type": "string",
+ "enum": ["siphon_full", "siphon_half", "drain"]
+ }
+ },
+ "scaling": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "level": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 20
+ },
+ "dice_count": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 99
+ },
+ "fixed_val": {
+ "type": "integer",
+ "minimum": -99,
+ "maximum": 99
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "required": [
+ "name",
+ "level",
+ "school",
+ "cast_time",
+ "cast_time_type",
+ "range_type",
+ "duration_type",
+ "aoe_type",
+ "scaling"
+ ],
+ "$defs": {
+ "ability-select": {
+ "type": "string",
+ "enum": ["strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"]
+ },
+ "damage-type-select": {
+ "type": "string",
+ "enum": [
+ "acid",
+ "bludgeoning",
+ "cold",
+ "fire",
+ "force",
+ "lightning",
+ "necrotic",
+ "piercing",
+ "poison",
+ "psychic",
+ "radiant",
+ "slashing",
+ "thunder"
+ ]
+ }
+ }
+}
diff --git a/src/services/api/items.js b/src/services/api/items.js
index 78c5ed50d..1ec645315 100644
--- a/src/services/api/items.js
+++ b/src/services/api/items.js
@@ -3,43 +3,54 @@ import axios from "axios";
const ITEMS_REF = "/items";
export class itemServices {
- constructor() {
- this.HK = axios.create({
- baseURL: process.env.VUE_APP_HK_API_ROOT
- });
- }
-
- async getItems(query, pageNumber = 1, pageSize = 15, fields=["ALL"], sortBy = "name", descending=false) {
- const skip = (pageNumber - 1)*pageSize;
- const fieldsString = fields.join(" ");
- let params = `?skip=${skip}&limit=${pageSize}&fields=${fieldsString}`;
-
- if(sortBy) {
- params += `&sort=${sortBy}${descending ? ":desc" : ""}`;
- }
-
- if(query) {
- const queryParams = [];
-
- if(query.search) {
- queryParams.push(`name=${query.search}`);
- }
-
- params += `&${queryParams.join("&")}`;
- }
-
- return this.HK.get(ITEMS_REF + params).then((response) => {
- return response.data;
- }).catch((error) => {
- throw error;
- });
- }
-
- async getItem(id) {
- return this.HK.get(`${ITEMS_REF}/${id}`).then((response) => {
- return response.data;
- }).catch((error) => {
- throw error;
- });
- }
-}
\ No newline at end of file
+ constructor() {
+ this.HK = axios.create({
+ baseURL: process.env.VUE_APP_HK_API_ROOT,
+ });
+ }
+
+ async getItems(
+ query,
+ pageNumber = 1,
+ pageSize = 15,
+ fields = ["ALL"],
+ sortBy = "name",
+ descending = false
+ ) {
+ const skip = (pageNumber - 1) * pageSize;
+ const fieldsString = fields.join(" ");
+ let params = `?skip=${skip}&limit=${pageSize}&fields=${fieldsString}`;
+
+ if (sortBy) {
+ params += `&sort=${sortBy}${descending ? ":desc" : ""}`;
+ }
+
+ if (query) {
+ const queryParams = [];
+
+ if (query.search) {
+ queryParams.push(`name=${query.search}`);
+ }
+
+ params += `&${queryParams.join("&")}`;
+ }
+
+ return this.HK.get(ITEMS_REF + params)
+ .then((response) => {
+ return response.data;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+
+ async getItem(id) {
+ return this.HK.get(`${ITEMS_REF}/${id}`)
+ .then((response) => {
+ return response.data;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+}
diff --git a/src/services/api/spells.js b/src/services/api/spells.js
index 37befc64b..ea8cf8fad 100644
--- a/src/services/api/spells.js
+++ b/src/services/api/spells.js
@@ -4,41 +4,69 @@ const SPELLS_REF = "/spells";
export class spellServices {
constructor() {
- this.HK = axios.create({
- baseURL: process.env.VUE_APP_HK_API_ROOT
- });
- }
-
- async getSpells(query, pageNumber = 1, pageSize = 15, fields=["ALL"], sortBy = "name", descending=false) {
- const skip = (pageNumber - 1)*pageSize;
- const fieldsString = fields.join(" ");
- let params = `?skip=${skip}&limit=${pageSize}&fields=${fieldsString}`;
-
- if(sortBy) {
- params += `&sort=${sortBy}${descending ? ":desc" : ""}`;
- }
-
- if(query) {
- const queryParams = [];
-
- if(query.search) {
- queryParams.push(`name=${query.search}`);
- }
- params += `&${queryParams.join("&")}`;
- }
-
- return this.HK.get(SPELLS_REF + params).then((response) => {
- return response.data;
- }).catch((error) => {
- throw error;
- });
- }
-
- async getSpell(id) {
- return this.HK.get(`${SPELLS_REF}/${id}`).then((response) => {
- return response.data;
- }).catch((error) => {
- throw error;
- });
- }
-}
\ No newline at end of file
+ this.HK = axios.create({
+ baseURL: process.env.VUE_APP_HK_API_ROOT,
+ });
+ }
+
+ async getSpells(
+ query,
+ pageNumber = 1,
+ pageSize = 15,
+ fields = ["ALL"],
+ sortBy = "name",
+ descending = false
+ ) {
+ const skip = (pageNumber - 1) * pageSize;
+ const fieldsString = fields.join(" ");
+ let params = `?skip=${skip}&limit=${pageSize}&fields=${fieldsString}`;
+
+ if (sortBy) {
+ params += `&sort=${sortBy}${descending ? ":desc" : ""}`;
+ }
+
+ if (query) {
+ const queryParams = [];
+ console.log(query);
+
+ if (query.search) {
+ queryParams.push(`name=${query.search}`);
+ }
+ if (query.schools && query.schools.length) {
+ for (const school of query.schools) {
+ console.log(school);
+ queryParams.push(`school[]=${school}`);
+ }
+ }
+ if (query.classes && query.classes.length) {
+ for (const cls of query.classes) {
+ queryParams.push(`classes[]=${cls}`);
+ }
+ }
+ if (query.levels && query.levels.length) {
+ for (const lvl of query.levels) {
+ queryParams.push(`level[]=${lvl}`);
+ }
+ }
+ params += `&${queryParams.join("&")}`;
+ }
+
+ return this.HK.get(SPELLS_REF + params)
+ .then((response) => {
+ return response.data;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+
+ async getSpell(id) {
+ return this.HK.get(`${SPELLS_REF}/${id}`)
+ .then((response) => {
+ return response.data;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+}
diff --git a/src/services/npcs.js b/src/services/npcs.js
index c77b8acf2..fc4bfdedf 100644
--- a/src/services/npcs.js
+++ b/src/services/npcs.js
@@ -1,4 +1,4 @@
-import { db, storage } from "src/firebase";
+import { firebase, db, storage } from "src/firebase";
const NPCS_REF = db.ref("npcs");
const SEARCH_NPCS_REF = db.ref("search_npcs");
@@ -10,201 +10,211 @@ const STORAGE_REF = storage.ref("npcs");
* Updates both 'npcs' and 'search_npcs' ref on CRUD
*/
export class npcServices {
-
- /**
- * Get all the npcs from the search_npcs reference
- *
- * @param {String} uid ID of active user
- * @returns All the content of search_npcs reference
- */
- async getNpcs(uid) {
- try {
- const npcs = await SEARCH_NPCS_REF.child(`${uid}/results`).once('value');
- return npcs.val();
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Get the number of NPCs that a user has from the search_npcs reference
- *
- * @param {String} uid ID of active user
- * @returns Number of npcs of a user
- */
- async getNpcCount(uid) {
- try {
- const path = `${uid}/metadata/count`;
- const count = await SEARCH_NPCS_REF.child(path).once('value');
- return count.val();
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Get an entire NPC from npcs reference
- *
- * @param {String} uid ID of active user
- * @param {String} id ID of the requested NPC
- * @returns An entire NPC from the npcs reference
- */
- async getNpc(uid, id) {
- try {
- const npc = await NPCS_REF.child(uid).child(id).once('value');
- return npc.val();
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Adds an NPC to the 'npcs' ref and the 'search_npcs' ref.
- *
- * @param {String} uid ID of active user
- * @param {Object} npc NPC to add
- * @param {Object} search_npc Compressed NPC
- * @returns Key of the newly added NPC
- */
- async addNpc(uid, npc, search_npc) {
- try {
- npc.name = npc.name.toLowerCase();
-
- // If there is an image upload save the blob in separate prop en then delete it from the NPC
- const blob = npc.blob;
- delete npc.blob;
-
- // Save the new NPC
- const newNpc = await NPCS_REF.child(uid).push(npc);
-
- // Upload image
- if(blob) {
- STORAGE_REF.child(`${uid}/${newNpc.key}.webp`).put(blob).then((snapshot) => {
- snapshot.ref.getDownloadURL().then(url => {
- search_npc.storage_avatar = url;
- npc.storage_avatar = url;
-
- // Update NPC
- NPCS_REF.child(`${uid}/${newNpc.key}/storage_avatar`).set(url);
- SEARCH_NPCS_REF.child(`${uid}/results/${newNpc.key}`).set(search_npc);
- });
- });
- } else {
- // Update search_npcs
- SEARCH_NPCS_REF.child(`${uid}/results/${newNpc.key}`).set(search_npc);
- }
- return newNpc.key;
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Updates an existing NPC in both 'npcs' and 'search_npcs' ref
- *
- * @param {String} uid ID of active user
- * @param {String} id ID of NPC to edit
- * @param {Object} npc Edited NPC
- * @param {Object} search_npc Compressed NPC
- */
- async editNpc(uid, id, npc, search_npc) {
- try {
- npc.name = npc.name.toLowerCase();
-
- // If there is an image upload save the blob in separate prop en then delete it from the NPC
- const blob = npc.blob;
- delete npc.blob;
-
- // Upload image
- const image_ref = STORAGE_REF.child(`${uid}/${id}.webp`);
-
- if(blob) {
- await image_ref.put(blob).then(async (snapshot) => {
- await snapshot.ref.getDownloadURL().then(async (url) => {
- npc.storage_avatar = url;
- search_npc.storage_avatar = url;
-
- // Save the NPC
- await NPCS_REF.child(uid).child(id).set(npc);
- await SEARCH_NPCS_REF.child(`${uid}/results/${id}`).set(search_npc);
- });
- });
- }
- // Delete the image when there is no blob and no storage_avatar
- else {
- if(!npc.storage_avatar) {
- image_ref.delete();
- }
-
- // Save the NPC
- await NPCS_REF.child(uid).child(id).set(npc);
- await SEARCH_NPCS_REF.child(`${uid}/results/${id}`).set(search_npc);
- }
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Updates a specific property in an existing NPC
- *
- * @param {String} uid ID of active user
- * @param {String} id ID of NPC to edit
- * @param {string} path Path to parent the property that must be updated (Only needed of the value is nested)
- * @param {object} value Object with { proptery: value }
- */
- async updateNpc(uid, id, path, value, update_search=false) {
- NPCS_REF.child(`${uid}/${id}${path}`).update(value).then(() => {
- if(update_search) {
- SEARCH_NPCS_REF.child(`${uid}/results/${id}${path}`).update(value);
- }
- return;
- }).catch((error) => {
- throw error;
- });
- }
-
- /**
- * Deletes an existing NPC in both 'npcs' and 'search_npcs' ref
- *
- * @param {String} uid ID of active user
- * @param {String} id ID of NPC to edit
- */
- async deleteNpc(uid, id) {
- try {
- NPCS_REF.child(uid).child(id).remove();
-
- // Update search_npcs
- SEARCH_NPCS_REF.child(`${uid}/results`).child(id).remove();
-
- // Delete any linked image
- STORAGE_REF.child(`${uid}/${id}.webp`).delete();
-
- return;
- } catch(error){
- throw error;
- }
- }
-
- async getFullNpcs(uid) {
- try {
- const all_npcs = await NPCS_REF.child(uid).once('value');
- return all_npcs.val();
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Update npc_count in the search table of search_npcs
- *
- * @param {String} uid User ID
- * @param {Int} diff Difference to add or subtract from npc count
- */
- async updateNpcCount(uid, diff) {
- const npc_count_path = `${uid}/metadata/count`;
- let npc_count = await this.getNpcCount(uid);
- await SEARCH_NPCS_REF.child(npc_count_path).set(npc_count + diff);
- return npc_count + diff;
- }
-}
\ No newline at end of file
+ /**
+ * Get all the npcs from the search_npcs reference
+ *
+ * @param {String} uid ID of active user
+ * @returns All the content of search_npcs reference
+ */
+ async getNpcs(uid) {
+ try {
+ const npcs = await SEARCH_NPCS_REF.child(`${uid}/results`).once("value");
+ return npcs.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Get the number of NPCs that a user has from the search_npcs reference
+ *
+ * @param {String} uid ID of active user
+ * @returns Number of npcs of a user
+ */
+ async getNpcCount(uid) {
+ try {
+ const path = `${uid}/metadata/count`;
+ const count = await SEARCH_NPCS_REF.child(path).once("value");
+ return count.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Get an entire NPC from npcs reference
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of the requested NPC
+ * @returns An entire NPC from the npcs reference
+ */
+ async getNpc(uid, id) {
+ try {
+ const npc = await NPCS_REF.child(uid).child(id).once("value");
+ return npc.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Adds an NPC to the 'npcs' ref and the 'search_npcs' ref.
+ *
+ * @param {String} uid ID of active user
+ * @param {Object} npc NPC to add
+ * @param {Object} search_npc Compressed NPC
+ * @returns Key of the newly added NPC
+ */
+ async addNpc(uid, npc, search_npc) {
+ try {
+ npc.name = npc.name.toLowerCase();
+
+ // If there is an image upload save the blob in separate prop en then delete it from the NPC
+ const blob = npc.blob;
+ delete npc.blob;
+
+ npc.created = firebase.database.ServerValue.TIMESTAMP;
+ npc.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ // Save the new NPC
+ const newNpc = await NPCS_REF.child(uid).push(npc);
+
+ // Upload image
+ if (blob) {
+ STORAGE_REF.child(`${uid}/${newNpc.key}.webp`)
+ .put(blob)
+ .then((snapshot) => {
+ snapshot.ref.getDownloadURL().then((url) => {
+ search_npc.storage_avatar = url;
+ npc.storage_avatar = url;
+
+ // Update NPC
+ NPCS_REF.child(`${uid}/${newNpc.key}/storage_avatar`).set(url);
+ SEARCH_NPCS_REF.child(`${uid}/results/${newNpc.key}`).set(search_npc);
+ });
+ });
+ } else {
+ // Update search_npcs
+ SEARCH_NPCS_REF.child(`${uid}/results/${newNpc.key}`).set(search_npc);
+ }
+ return newNpc.key;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Updates an existing NPC in both 'npcs' and 'search_npcs' ref
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of NPC to edit
+ * @param {Object} npc Edited NPC
+ * @param {Object} search_npc Compressed NPC
+ */
+ async editNpc(uid, id, npc, search_npc) {
+ try {
+ npc.name = npc.name.toLowerCase();
+
+ // If there is an image upload save the blob in separate prop en then delete it from the NPC
+ const blob = npc.blob;
+ delete npc.blob;
+
+ npc.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ // Upload image
+ const image_ref = STORAGE_REF.child(`${uid}/${id}.webp`);
+
+ if (blob) {
+ await image_ref.put(blob).then(async (snapshot) => {
+ await snapshot.ref.getDownloadURL().then(async (url) => {
+ npc.storage_avatar = url;
+ search_npc.storage_avatar = url;
+
+ // Save the NPC
+ await NPCS_REF.child(uid).child(id).set(npc);
+ await SEARCH_NPCS_REF.child(`${uid}/results/${id}`).set(search_npc);
+ });
+ });
+ }
+ // Delete the image when there is no blob and no storage_avatar
+ else {
+ if (!npc.storage_avatar) {
+ image_ref.delete().catch(); // Catch 404 image not found
+ }
+
+ // Save the NPC
+ await NPCS_REF.child(uid).child(id).set(npc);
+ await SEARCH_NPCS_REF.child(`${uid}/results/${id}`).set(search_npc);
+ }
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Updates a specific property in an existing NPC
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of NPC to edit
+ * @param {string} path Path to parent the property that must be updated (Only needed of the value is nested)
+ * @param {object} value Object with { proptery: value }
+ */
+ async updateNpc(uid, id, path, value, update_search = false) {
+ NPCS_REF.child(`${uid}/${id}${path}`)
+ .update(value)
+ .then(() => {
+ if (update_search) {
+ SEARCH_NPCS_REF.child(`${uid}/results/${id}${path}`).update(value);
+ }
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ NPCS_REF.child(`${uid}/${id}/updated`).set(firebase.database.ServerValue.TIMESTAMP);
+ }
+
+ /**
+ * Deletes an existing NPC in both 'npcs' and 'search_npcs' ref
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of NPC to edit
+ */
+ async deleteNpc(uid, id) {
+ try {
+ NPCS_REF.child(uid).child(id).remove();
+
+ // Update search_npcs
+ SEARCH_NPCS_REF.child(`${uid}/results`).child(id).remove();
+
+ // Delete any linked image
+ STORAGE_REF.child(`${uid}/${id}.webp`).delete();
+
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getFullNpcs(uid) {
+ try {
+ const all_npcs = await NPCS_REF.child(uid).once("value");
+ return all_npcs.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Update npc_count in the search table of search_npcs
+ *
+ * @param {String} uid User ID
+ * @param {Int} diff Difference to add or subtract from npc count
+ */
+ async updateNpcCount(uid, diff) {
+ const npc_count_path = `${uid}/metadata/count`;
+ let npc_count = await this.getNpcCount(uid);
+ await SEARCH_NPCS_REF.child(npc_count_path).set(npc_count + diff);
+ return npc_count + diff;
+ }
+}
diff --git a/src/services/players.js b/src/services/players.js
index b32f7ce74..cd3b3579c 100644
--- a/src/services/players.js
+++ b/src/services/players.js
@@ -1,4 +1,4 @@
-import { db, storage } from "src/firebase";
+import { firebase, db, storage } from "src/firebase";
const PLAYERS_REF = db.ref("players");
const SEARCH_PLAYERS_REF = db.ref("search_players");
@@ -6,236 +6,250 @@ const CHARACTER_CONTROL_REF = db.ref("character_control");
const STORAGE_REF = storage.ref("players");
export class playerServices {
-
- async getPlayers(uid) {
- try {
- const players = await SEARCH_PLAYERS_REF.child(`${uid}/results`).once('value');
- return players.val();
- } catch(error) {
- throw error;
- }
- }
-
- async getPlayerCount(uid) {
- try {
- const path = `${uid}/metadata/count`;
- const count = await SEARCH_PLAYERS_REF.child(path).once('value');
- return count.val();
- } catch(error) {
- throw error;
- }
- }
-
- async getPlayer(uid, id) {
- try {
- const player = await PLAYERS_REF.child(uid).child(id).once('value');
- return player.val();
- } catch(error) {
- throw error;
- }
- }
-
- async getSearchPlayer(uid, id) {
- try {
- const search_player = await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).once('value');
- return search_player.val();
- } catch(error) {
- throw error;
- }
- }
-
- async getCharacters(uid) {
- try {
- const characters = await CHARACTER_CONTROL_REF.child(uid).once('value');
- return characters.val();
- } catch(error) {
- throw error;
- }
- }
-
- // Fetches the UID of the owner of a player
- async getOwner(uid, playerId) {
- try {
- const userId = await CHARACTER_CONTROL_REF.child(uid).child(playerId).once('value');
- return userId.val();
- } catch(error) {
- throw error;
- }
- }
-
- async getPlayerProp(uid, id, property) {
- const path = `${uid}/${id}/${property}`;
- try {
- const value = await PLAYERS_REF.child(path).once('value');
- return value.val();
- } catch(error) {
- throw error;
- }
- }
-
- async addPlayer(uid, player, search_player) {
- try {
- // If there is an image upload save the blob in separate prop en then delete it from the player
- const blob = player.blob;
- delete player.blob;
-
- const newPlayer = await PLAYERS_REF.child(uid).push(player);
-
- // Upload image
- if(blob) {
- STORAGE_REF.child(`${uid}/${newPlayer.key}.webp`).put(blob).then((snapshot) => {
- snapshot.ref.getDownloadURL().then(url => {
- search_player.storage_avatar = url;
- player.storage_avatar = url;
-
- // Update NPC
- PLAYERS_REF.child(`${uid}/${newPlayer.key}/storage_avatar`).set(url);
- SEARCH_PLAYERS_REF.child(`${uid}/results/${newPlayer.key}`).set(search_player);
- });
- });
- } else {
- // Update search_players
- SEARCH_PLAYERS_REF.child(`${uid}/results/${newPlayer.key}`).set(search_player);
- }
-
- return newPlayer.key;
- } catch(error) {
- throw error;
- }
- }
-
- async editPlayer(uid, id, player, search_player) {
- try {
- // If there is an image upload save the blob in separate prop en then delete it from the player
- const blob = player.blob;
- delete player.blob;
-
- // Upload image
- const image_ref = STORAGE_REF.child(`${uid}/${id}.webp`);
-
- if(blob) {
- await image_ref.put(blob).then(async (snapshot) => {
- await snapshot.ref.getDownloadURL().then(async (url) => {
- player.storage_avatar = url;
- search_player.storage_avatar = url;
-
- // Save the player
- await PLAYERS_REF.child(uid).child(id).set(player);
- await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).set(search_player);
- });
- });
- }
- // Delete the image when there is no blob and no storage_avatar
- else {
- if(!player.storage_avatar) {
- image_ref.delete();
- }
-
- // Save the player
- await PLAYERS_REF.child(uid).child(id).set(player);
- await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).set(search_player);
- }
- } catch(error) {
- throw error;
- }
- }
-
- /**
- * Updates a specific property in an existing player
- *
- * @param {String} uid ID of active user
- * @param {String} id ID of player to edit
- * @param {string} path Path to parent the property that must be updated (Only needed of the value is nested)
- * @param {object} value Object with { property: value }
- * @param {boolean} update_search Wether or not search_players must be updated
- */
- async updatePlayer(uid, id, path, value, update_search=false) {
- PLAYERS_REF.child(`${uid}/${id}${path}`).update(value).then(() => {
- if(update_search) {
- SEARCH_PLAYERS_REF.child(`${uid}/results/${id}${path}`).update(value);
- }
- }).catch((error) => {
- throw error;
- });
- }
-
- /**
- * Sync player with Character Sync Extension data
- *
- * @param {string} uid
- * @param {string} id
- * @param {*} player
- * @param {*} search_player
- * @returns
- */
- async syncPlayer(uid, id, player, search_player) {
- PLAYERS_REF.child(`${uid}/${id}`).update(player).then(() => {
- SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).update(search_player);
- }).catch((error) => {
- throw error;
- });
- }
-
- async deletePlayer(uid, id, control) {
- try {
- PLAYERS_REF.child(uid).child(id).remove();
-
- // Remove from controlled characters
- if(control) {
- CHARACTER_CONTROL_REF.child(control).child(id).remove();
- }
-
- //Update search_players
- SEARCH_PLAYERS_REF.child(`${uid}/results`).child(id).remove();
- return;
- } catch(error){
- throw error;
- }
- }
-
- /**
- * Give control over a character to another user
- *
- * @param {string} uid uid of the owner of the characte
- * @param {string} id id of the character
- * @param {string} user_id uid of the user getting control
- */
- async giveControl(uid, id, user_id) {
- const path = `${user_id}/${id}/user`;
- CHARACTER_CONTROL_REF.child(path).set(uid).then(() => {
- return;
- }).catch((error) => {
- throw error;
- });
- }
-
-
- /**
- * Gives up control over character
- *
- * @param {string} uid
- * @param {string} id
- * @returns
- */
- async removeControl(uid, id) {
- try {
- await CHARACTER_CONTROL_REF.child(uid).child(id).remove();
- return;
- } catch(error){
- throw error;
- }
- }
-
- /**
- * Update player_count in the search table of search_players
- *
- * @param {String} uid User ID
- * @param {Int} diff Difference to add or subtract from player count
- */
- async updatePlayerCount(uid, diff) {
- const player_count_path = `${uid}/metadata/count`;
- let player_count = await this.getPlayerCount(uid);
- await SEARCH_PLAYERS_REF.child(player_count_path).set(player_count + diff);
- return player_count + diff;
- }
-}
\ No newline at end of file
+ async getPlayers(uid) {
+ try {
+ const players = await SEARCH_PLAYERS_REF.child(`${uid}/results`).once("value");
+ return players.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getPlayerCount(uid) {
+ try {
+ const path = `${uid}/metadata/count`;
+ const count = await SEARCH_PLAYERS_REF.child(path).once("value");
+ return count.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getPlayer(uid, id) {
+ try {
+ const player = await PLAYERS_REF.child(uid).child(id).once("value");
+ return player.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getSearchPlayer(uid, id) {
+ try {
+ const search_player = await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).once("value");
+ return search_player.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getCharacters(uid) {
+ try {
+ const characters = await CHARACTER_CONTROL_REF.child(uid).once("value");
+ return characters.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ // Fetches the UID of the owner of a player
+ async getOwner(uid, playerId) {
+ try {
+ const userId = await CHARACTER_CONTROL_REF.child(uid).child(playerId).once("value");
+ return userId.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async getPlayerProp(uid, id, property) {
+ const path = `${uid}/${id}/${property}`;
+ try {
+ const value = await PLAYERS_REF.child(path).once("value");
+ return value.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async addPlayer(uid, player, search_player) {
+ try {
+ // If there is an image upload save the blob in separate prop en then delete it from the player
+ const blob = player.blob;
+ delete player.blob;
+
+ player.created = firebase.database.ServerValue.TIMESTAMP;
+ player.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ const newPlayer = await PLAYERS_REF.child(uid).push(player);
+
+ // Upload image
+ if (blob) {
+ STORAGE_REF.child(`${uid}/${newPlayer.key}.webp`)
+ .put(blob)
+ .then((snapshot) => {
+ snapshot.ref.getDownloadURL().then((url) => {
+ search_player.storage_avatar = url;
+ player.storage_avatar = url;
+
+ // Update NPC
+ PLAYERS_REF.child(`${uid}/${newPlayer.key}/storage_avatar`).set(url);
+ SEARCH_PLAYERS_REF.child(`${uid}/results/${newPlayer.key}`).set(search_player);
+ });
+ });
+ } else {
+ // Update search_players
+ SEARCH_PLAYERS_REF.child(`${uid}/results/${newPlayer.key}`).set(search_player);
+ }
+
+ return newPlayer.key;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ async editPlayer(uid, id, player, search_player) {
+ try {
+ // If there is an image upload save the blob in separate prop en then delete it from the player
+ const blob = player.blob;
+ delete player.blob;
+
+ player.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ // Upload image
+ const image_ref = STORAGE_REF.child(`${uid}/${id}.webp`);
+
+ if (blob) {
+ await image_ref.put(blob).then(async (snapshot) => {
+ await snapshot.ref.getDownloadURL().then(async (url) => {
+ player.storage_avatar = url;
+ search_player.storage_avatar = url;
+
+ // Save the player
+ await PLAYERS_REF.child(uid).child(id).set(player);
+ await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).set(search_player);
+ });
+ });
+ }
+ // Delete the image when there is no blob and no storage_avatar
+ else {
+ if (!player.storage_avatar) {
+ image_ref.delete().catch(); // Catch 404 image not found
+ }
+ // Save the player
+ await PLAYERS_REF.child(uid).child(id).set(player);
+ await SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).set(search_player);
+ }
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Updates a specific property in an existing player
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of player to edit
+ * @param {string} path Path to parent the property that must be updated (Only needed of the value is nested)
+ * @param {object} value Object with { property: value }
+ * @param {boolean} update_search Wether or not search_players must be updated
+ */
+ async updatePlayer(uid, id, path, value, update_search = false) {
+ PLAYERS_REF.child(`${uid}/${id}${path}`)
+ .update(value)
+ .then(() => {
+ if (update_search) {
+ SEARCH_PLAYERS_REF.child(`${uid}/results/${id}${path}`).update(value);
+ }
+ })
+ .catch((error) => {
+ throw error;
+ });
+ PLAYERS_REF.child(`${uid}/${id}/updated`).set(firebase.database.ServerValue.TIMESTAMP);
+ }
+
+ /**
+ * Sync player with Character Sync Extension data
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {*} player
+ * @param {*} search_player
+ * @returns
+ */
+ async syncPlayer(uid, id, player, search_player) {
+ PLAYERS_REF.child(`${uid}/${id}`)
+ .update(player)
+ .then(() => {
+ SEARCH_PLAYERS_REF.child(`${uid}/results/${id}`).update(search_player);
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+
+ async deletePlayer(uid, id, control) {
+ try {
+ PLAYERS_REF.child(uid).child(id).remove();
+
+ // Remove from controlled characters
+ if (control) {
+ CHARACTER_CONTROL_REF.child(control).child(id).remove();
+ }
+
+ //Update search_players
+ SEARCH_PLAYERS_REF.child(`${uid}/results`).child(id).remove();
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Give control over a character to another user
+ *
+ * @param {string} uid uid of the owner of the characte
+ * @param {string} id id of the character
+ * @param {string} user_id uid of the user getting control
+ */
+ async giveControl(uid, id, user_id) {
+ const path = `${user_id}/${id}/user`;
+ CHARACTER_CONTROL_REF.child(path)
+ .set(uid)
+ .then(() => {
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+
+ /**
+ * Gives up control over character
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @returns
+ */
+ async removeControl(uid, id) {
+ try {
+ await CHARACTER_CONTROL_REF.child(uid).child(id).remove();
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Update player_count in the search table of search_players
+ *
+ * @param {String} uid User ID
+ * @param {Int} diff Difference to add or subtract from player count
+ */
+ async updatePlayerCount(uid, diff) {
+ const player_count_path = `${uid}/metadata/count`;
+ let player_count = await this.getPlayerCount(uid);
+ await SEARCH_PLAYERS_REF.child(player_count_path).set(player_count + diff);
+ return player_count + diff;
+ }
+}
diff --git a/src/services/spells.js b/src/services/spells.js
new file mode 100644
index 000000000..f51119323
--- /dev/null
+++ b/src/services/spells.js
@@ -0,0 +1,145 @@
+import { firebase, db } from "src/firebase";
+
+const SPELLS_REF = db.ref("spells");
+const SEARCH_SPELLS_REF = db.ref("search_spells");
+
+/**
+ * Spell Firebase Service
+ * CRUD interface implementation for Firebase
+ * Updates both 'custom_spells' and 'search_custom_spells' ref on CRUD
+ */
+export class SpellServices {
+ /**
+ * Get all the spells from the search_custom_spells reference
+ *
+ * @param {String} uid ID of active user
+ * @returns All the content of search_custom_spells reference
+ */
+ async getSpells(uid) {
+ try {
+ const spells = await SEARCH_SPELLS_REF.child(`${uid}/results`).once("value", (snapshot) => {
+ return snapshot;
+ });
+ return spells.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Get the number of spells that a user has from the search_custom_spells reference
+ *
+ * @param {String} uid ID of active user
+ * @returns Number of spells of a user
+ */
+ async getSpellCount(uid) {
+ try {
+ const path = `${uid}/metadata/count`;
+ let count = await SEARCH_SPELLS_REF.child(path).once("value");
+ return count.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Get an entire spell from 'custom_spells' reference
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of the requested spell
+ * @returns An entire spell from the 'custom_spells' reference
+ */
+ async getSpell(uid, id) {
+ try {
+ const spell = await SPELLS_REF.child(uid)
+ .child(id)
+ .once("value", (snapshot) => {
+ return snapshot;
+ });
+ return spell.val();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Adds an spell to the 'custom_spells' ref and the 'search_custom_spells' ref.
+ * Also updates the count metadata in 'search_custom_spells'
+ *
+ * @param {String} uid ID of active user
+ * @param {Object} spell Spell to add
+ * @param {Int} new_count Updated number of spells
+ * @param {Object} search_spell Compressed spell
+ * @returns Key of the newly added spell
+ */
+ async addSpell(uid, spell, search_spell) {
+ try {
+ spell.name = spell.name.toLowerCase();
+ const newSpell = await SPELLS_REF.child(uid).push(spell);
+
+ spell.created = firebase.database.ServerValue.TIMESTAMP;
+ spell.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ // Update search_spells
+ SEARCH_SPELLS_REF.child(`${uid}/results/${newSpell.key}`).set(search_spell);
+
+ return newSpell.key;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Updates an existing spell in both 'custom_spells' and 'search_custom_spells' ref
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of spell to edit
+ * @param {Object} spell Edited spell
+ * @param {Object} search_spell Compressed spell
+ */
+ async editSpell(uid, id, spell, search_spell) {
+ spell.name = spell.name.toLowerCase();
+ spell.updated = firebase.database.ServerValue.TIMESTAMP;
+
+ SPELLS_REF.child(uid)
+ .child(id)
+ .set(spell)
+ .then(() => {
+ SEARCH_SPELLS_REF.child(`${uid}/results/${id}`).set(search_spell);
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+
+ /**
+ * Deletes an existing spell in both 'custom_spells' and 'search_custom_spells' ref
+ *
+ * @param {String} uid ID of active user
+ * @param {String} id ID of spell to edit
+ */
+ async deleteSpell(uid, id) {
+ try {
+ SPELLS_REF.child(uid).child(id).remove();
+
+ //Update search_custom_spells
+ SEARCH_SPELLS_REF.child(`${uid}/results`).child(id).remove();
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ /**
+ * Update spell_count in the search table of search_spells
+ *
+ * @param {String} uid User ID
+ * @param {Int} diff Difference to add or subtract from spell count
+ */
+ async updateSpellCount(uid, diff) {
+ const spell_count_path = `${uid}/metadata/count`;
+ let spell_count = await SEARCH_SPELLS_REF.child(spell_count_path).once("value");
+ await SEARCH_SPELLS_REF.child(spell_count_path).set(spell_count.val() + diff);
+ return spell_count.val() + diff;
+ }
+}
diff --git a/src/services/user.js b/src/services/user.js
index 028e6ee65..94bbb303e 100644
--- a/src/services/user.js
+++ b/src/services/user.js
@@ -8,126 +8,138 @@ const SEARCH_USERS_REF = db.ref("search_users");
const VOUCHER_HISTORY_REF = db.ref("voucher_history");
/**
-* User Firebase Service
-* CRUD interface implementation for Firebase
-* Updates 'users'
-*/
+ * User Firebase Service
+ * CRUD interface implementation for Firebase
+ * Updates 'users'
+ */
export class userServices {
+ /**
+ * Get the user object from firebase
+ *
+ * @param {String} uid ID of active user
+ * @returns Full user object from /users/uid
+ */
+ async getFullUser(uid) {
+ try {
+ const user = await USERS_REF.child(uid).once("value");
+ return user.val();
+ } catch (error) {
+ throw error;
+ }
+ }
- /**
- * Get the user object from firebase
- *
- * @param {String} uid ID of active user
- * @returns Full user object from /users/uid
- */
- async getFullUser(uid) {
- try {
- const user = await USERS_REF.child(uid).once('value');
- return user.val();
- } catch(error) {
- throw error;
- }
- }
+ /**
+ * Get the seasch_user object from firebase
+ *
+ * @param {String} uid ID of active user
+ * @returns Full user object from /search_user/uid
+ */
+ async getSearchUser(uid) {
+ try {
+ const user = await SEARCH_USERS_REF.child(uid).once("value");
+ return user.val();
+ } catch (error) {
+ throw error;
+ }
+ }
- /**
- * Get the seasch_user object from firebase
- *
- * @param {String} uid ID of active user
- * @returns Full user object from /search_user/uid
- */
- async getSearchUser(uid) {
- try {
- const user = await SEARCH_USERS_REF.child(uid).once('value');
- return user.val();
- } catch(error) {
- throw error;
- }
- }
+ /**
+ * Get the search_user object from firebase
+ *
+ * @param {String} uid ID of active user
+ * @returns All user settings from /settings/uid
+ */
+ async getSettings(uid) {
+ try {
+ const settings = await SETTINGS_REF.child(uid).once("value");
+ return settings.val();
+ } catch (error) {
+ throw error;
+ }
+ }
- /**
- * Get the seasch_user object from firebase
- *
- * @param {String} uid ID of active user
- * @returns All user settings from /settings/uid
- */
- async getSettings(uid) {
- try {
- const settings = await SETTINGS_REF.child(uid).once('value');
- return settings.val();
- } catch(error) {
- throw error;
- }
- }
+ /**
+ * Update a setting for a user
+ *
+ * @param {String} uid ID of active user
+ * @param {String} category general|encounter|track
+ * @param {string} sub_category undefined|npcs|players
+ * @param {string} type
+ * @param {any} value
+ */
+ async updateSettings(uid, category, sub_category, type, value) {
+ value = value === undefined ? null : value;
+ let path = `${uid}/${category}`;
+ path = sub_category ? `${path}/${sub_category}` : `${path}`;
+ SETTINGS_REF.child(path)
+ .update({ [type]: value })
+ .then(() => {
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
- /**
- * Update a setting for a user
- *
- * @param {String} uid ID of active user
- * @param {String} category general|encounter|track
- * @param {string} sub_category undefined|npcs|players
- * @param {string} type
- * @param {any} value
- */
- async updateSettings(uid, category, sub_category, type, value) {
- value = (value === undefined) ? null : value;
- let path = `${uid}/${category}`;
- path = (sub_category) ? `${path}/${sub_category}` : `${path}`;
- SETTINGS_REF.child(path).update({ [type]: value }).then(() => {
- return;
- }).catch((error) => {
- throw error;
- });
- }
+ /**
+ * Update a setting for a user
+ *
+ * @param {String} uid ID of active user
+ * @param {String} category general|encounter|track
+ */
+ async setDefaultSettings(uid, category) {
+ SETTINGS_REF.child(uid)
+ .child(category)
+ .remove()
+ .then(() => {
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
- /**
- * Update a setting for a user
- *
- * @param {String} uid ID of active user
- * @param {String} category general|encounter|track
- */
- async setDefaultSettings(uid, category) {
- SETTINGS_REF.child(uid).child(category).remove().then(() => {
- return;
- }).catch((error) => {
- throw error;
- });
- }
+ /*
+ *
+ */
- /*
- *
- */
+ static async setActiveVoucher(uid, voucher_object) {
+ let date = await serverUtils.getServerTime();
+ date.setMonth(date.getMonth() + voucher_object.duration);
- static async setActiveVoucher(uid, voucher_object) {
- let date = await serverUtils.getServerTime();
- date.setMonth(date.getMonth() + voucher_object.duration);
+ const fbVoucher = {
+ id: voucher_object.tier,
+ date: date.toLocaleDateString("en-US"),
+ };
+ const voucherHistItem = {
+ ...voucher_object,
+ applied_on: (await serverUtils.getServerTime()).toLocaleDateString("en-US"),
+ };
- const fbVoucher = {
- id: voucher_object.tier,
- date: date.toLocaleDateString('en-US')
- }
- const voucherHistItem = {
- ...voucher_object,
- applied_on: (await serverUtils.getServerTime()).toLocaleDateString('en-US')
- }
+ const usedBefore = await VOUCHER_HISTORY_REF.child(uid)
+ .child(voucherHistItem.voucher)
+ .once("value");
+ if (usedBefore.val()) {
+ throw "Voucher has already been redeemed.";
+ }
- const usedBefore = await VOUCHER_HISTORY_REF.child(uid).child(voucherHistItem.voucher).once('value');
- if (usedBefore.val()) {
- throw "Voucher has already been redeemed.";
- }
+ await Promise.all([
+ USERS_REF.child(uid).child("voucher").set(fbVoucher),
+ VOUCHER_HISTORY_REF.child(uid).child(voucherHistItem.voucher).set(voucherHistItem),
+ voucherService.incrementVoucherUsage(voucherHistItem.voucher),
+ ]);
+ return fbVoucher;
+ }
- await Promise.all([
- USERS_REF.child(uid).child('voucher').set(fbVoucher),
- VOUCHER_HISTORY_REF.child(uid).child(voucherHistItem.voucher).set(voucherHistItem),
- voucherService.incrementVoucherUsage(voucherHistItem.voucher)
- ])
- return fbVoucher;
- }
-
- static async removeVoucher(uid) {
- USERS_REF.child(uid).child('voucher').remove().then(() => {
- return;
- }).catch((error) => {
- throw error;
- });
- }
+ static async removeVoucher(uid) {
+ USERS_REF.child(uid)
+ .child("voucher")
+ .remove()
+ .then(() => {
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
}
diff --git a/src/services/vouchers.js b/src/services/vouchers.js
index 55dde57bb..de424a4c2 100644
--- a/src/services/vouchers.js
+++ b/src/services/vouchers.js
@@ -1,8 +1,9 @@
-import { db } from 'src/firebase'
-import { serverUtils } from 'src/services/serverUtils'
+import firebase from "firebase/app";
+import { db } from "src/firebase";
+import { serverUtils } from "src/services/serverUtils";
-const VOUCHER_REF = db.ref('vouchers');
-const TIERS_REF = db.ref('tiers');
+const VOUCHER_REF = db.ref("vouchers");
+const TIERS_REF = db.ref("tiers");
/*
* Voucher Firebase Service
@@ -10,50 +11,49 @@ const TIERS_REF = db.ref('tiers');
* Updates 'vouchers' and 'voucher_history'
*/
export class voucherService {
- static async getAllVouchers() {
- return (await VOUCHER_REF.once('value')).val();
- }
-
- static getVouchersWithCallback(callback) {
- return VOUCHER_REF.on('value', callback);
- }
-
- static async getValidVouchers() {
- const vouchers = (await VOUCHER_REF.once('value')).val();
- const server_time = await serverUtils.getServerTime();
- return Object.values(vouchers).filter(voucher => {
- const valid_until = new Date(voucher.valid_until);
- valid_until.setDate(valid_until.getDate() + 1);
- return voucher.disabled === undefined && server_time < valid_until;
- });
- }
-
- static async addNewVoucher(voucher_object) {
- voucher_object.voucher = voucher_object.voucher.toUpperCase();
- voucher_object.times_used = 0;
- return VOUCHER_REF.child(voucher_object.voucher).set(voucher_object);
- }
-
- static async deleteVoucher(voucher_name) {
- return VOUCHER_REF.child(voucher_name).remove();
- }
-
- static async disableVoucher(voucher_name) {
- return VOUCHER_REF.child(voucher_name).child('disabled').set(true);
- }
-
- static async enableVoucher(voucher_name) {
- return VOUCHER_REF.child(voucher_name).child('disabled').remove();
- }
-
- static async incrementVoucherUsage(voucher_name) {
- return VOUCHER_REF.child(voucher_name).child('times_used').transaction((value) => {
- return value++;
- })
- }
-
- static async getVoucherTiers() {
- const all_tiers = (await TIERS_REF.once('value')).val()
- return all_tiers;
- }
+ static async getAllVouchers() {
+ return (await VOUCHER_REF.once("value")).val();
+ }
+
+ static getVouchersWithCallback(callback) {
+ return VOUCHER_REF.on("value", callback);
+ }
+
+ static async getValidVouchers() {
+ const vouchers = (await VOUCHER_REF.once("value")).val();
+ const server_time = await serverUtils.getServerTime();
+ return Object.values(vouchers).filter((voucher) => {
+ const valid_until = new Date(voucher.valid_until);
+ valid_until.setDate(valid_until.getDate() + 1);
+ return voucher.disabled === undefined && server_time < valid_until;
+ });
+ }
+
+ static async addNewVoucher(voucher_object) {
+ voucher_object.voucher = voucher_object.voucher.toUpperCase();
+ voucher_object.times_used = 0;
+ return VOUCHER_REF.child(voucher_object.voucher).set(voucher_object);
+ }
+
+ static async deleteVoucher(voucher_name) {
+ return VOUCHER_REF.child(voucher_name).remove();
+ }
+
+ static async disableVoucher(voucher_name) {
+ return VOUCHER_REF.child(voucher_name).child("disabled").set(true);
+ }
+
+ static async enableVoucher(voucher_name) {
+ return VOUCHER_REF.child(voucher_name).child("disabled").remove();
+ }
+
+ static async incrementVoucherUsage(voucher_name) {
+ return VOUCHER_REF.child(voucher_name)
+ .child("times_used")
+ .set(firebase.database.ServerValue.increment(1));
+ }
+
+ static async getVoucherTiers() {
+ return (await TIERS_REF.once("value")).val();
+ }
}
diff --git a/src/store/index.js b/src/store/index.js
index a1a9d64e0..150caf5d9 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1,21 +1,22 @@
-import Vue from 'vue';
-import Vuex from 'vuex';
-import general from './modules/general';
-import tips from './modules/tips';
-import { run_encounter } from './modules/runEncounter';
-import user from './modules/user';
-import api_spells from './modules/content/spells.js';
-import api_monsters from './modules/content/monsters.js';
-import api_items from './modules/content/items.js';
-import api_conditions from './modules/content/conditions.js';
-import campaigns from './modules/userContent/campaigns.js';
-import npcs from './modules/userContent/npcs.js';
-import items from './modules/userContent/items.js';
-import reminders from './modules/userContent/reminders.js';
-import players from './modules/userContent/players.js';
-import encounters from './modules/userContent/encounters.js';
-import characters from './modules/userContent/characters.js';
-import trackCampaign from './modules/trackCampaign.js';
+import Vue from "vue";
+import Vuex from "vuex";
+import general from "./modules/general";
+import tips from "./modules/tips";
+import { run_encounter } from "./modules/runEncounter";
+import user from "./modules/user";
+import api_spells from "./modules/content/spells.js";
+import api_monsters from "./modules/content/monsters.js";
+import api_items from "./modules/content/items.js";
+import api_conditions from "./modules/content/conditions.js";
+import campaigns from "./modules/userContent/campaigns.js";
+import npcs from "./modules/userContent/npcs.js";
+import items from "./modules/userContent/items.js";
+import spells from "./modules/userContent/spells.js";
+import reminders from "./modules/userContent/reminders.js";
+import players from "./modules/userContent/players.js";
+import encounters from "./modules/userContent/encounters.js";
+import characters from "./modules/userContent/characters.js";
+import trackCampaign from "./modules/trackCampaign.js";
Vue.use(Vuex);
@@ -29,9 +30,9 @@ Vue.use(Vuex);
*/
export default function () {
- return new Vuex.Store({
- modules: {
- general: general,
+ return new Vuex.Store({
+ modules: {
+ general: general,
tips: tips,
user: user,
encounter: run_encounter,
@@ -42,15 +43,16 @@ export default function () {
api_conditions: api_conditions,
npcs: npcs,
items: items,
+ spells: spells,
reminders: reminders,
players: players,
encounters: encounters,
characters: characters,
- trackCampaign: trackCampaign
- },
- // enable strict mode (adds overhead!)
- // for dev mode only
- // strict: process.env.DEBUGGING
- strict: false
- });
+ trackCampaign: trackCampaign,
+ },
+ // enable strict mode (adds overhead!)
+ // for dev mode only
+ // strict: process.env.DEBUGGING
+ strict: false,
+ });
}
diff --git a/src/store/modules/general.js b/src/store/modules/general.js
index ca81b18ca..e9bd04d63 100644
--- a/src/store/modules/general.js
+++ b/src/store/modules/general.js
@@ -1,5 +1,5 @@
-import Vue from 'vue';
-import { browserDetect } from '../../functions';
+import Vue from "vue";
+import { browserDetect } from "../../functions";
export default {
state: () => ({
@@ -10,31 +10,47 @@ export default {
action_rolls: [],
side_collapsed: true,
side_small_screen: false,
- browser: browserDetect()
+ browser: browserDetect(),
}),
getters: {
- initialized: (state) => { return state.initialized },
- theme: (state) => { return state.theme },
- getSlide(state) { return state.slide; },
- rolls(state) { return state.rolls; },
- action_rolls(state) { return state.action_rolls; },
- side_collapsed(state) { return state.side_collapsed; },
- side_small_screen(state) { return state.side_small_screen; },
- browser(state) { return state.browser; },
+ initialized: (state) => {
+ return state.initialized;
+ },
+ theme: (state) => {
+ return state.theme;
+ },
+ getSlide(state) {
+ return state.slide;
+ },
+ rolls(state) {
+ return state.rolls;
+ },
+ action_rolls(state) {
+ return state.action_rolls;
+ },
+ side_collapsed(state) {
+ return state.side_collapsed;
+ },
+ side_small_screen(state) {
+ return state.side_small_screen;
+ },
+ browser(state) {
+ return state.browser;
+ },
},
actions: {
// Initialize basic settings depending on a user being logged in or not.
async initialize({ state, dispatch, commit, rootGetters }) {
- if(state.initialized) return;
-
+ if (state.initialized) return;
+
dispatch("setTips");
// In main.js before the Vue instance is rendered
// it's checked if there is a firebase authorization present.
// Therefore we can check here with 'auth' if there is a user.
- if(rootGetters.user) {
+ if (rootGetters.user) {
// await dispatch("setUser");
// first set the user settings in order to set theme correctly
await dispatch("set_user_settings")
@@ -49,7 +65,8 @@ export default {
dispatch("items/fetch_item_count"),
dispatch("campaigns/fetch_campaign_count"),
dispatch("encounters/fetch_encounter_count"),
- dispatch("reminders/fetch_reminder_count")
+ dispatch("reminders/fetch_reminder_count"),
+ dispatch("spells/fetch_spell_count"),
]);
})
.then(async () => {
@@ -63,7 +80,7 @@ export default {
commit("SET_INITIALIZED", true);
})
- .catch(error => {
+ .catch((error) => {
const roll = Math.floor(Math.random() * 15);
console.log(
`%cRolled ${roll} for a DC 15 initialize check.\nInitialization of Harmless Key failed.`,
@@ -74,13 +91,13 @@ export default {
} else {
dispatch("setTheme");
commit("SET_INITIALIZED", true);
- }
+ }
},
/**
- * Forces reinitialization
+ * Forces re-initialization
* Needed when signing in
* First store was initialize without a user, but now it has a user.
- * By simply setting initialized to false
+ * By simply setting initialized to false
* the beforeEach() in main.js will take care of the rest
*/
async reinitialize({ commit, dispatch }) {
@@ -89,28 +106,31 @@ export default {
},
setTheme({ commit, state, rootGetters, dispatch }, theme) {
const uid = rootGetters.user ? rootGetters.user.uid : undefined;
-
+
// If no theme is specified, it's called from initialize() so set it to the previously choosen theme if it exists, or dark otherwise.
- if(!theme) {
+ if (!theme) {
theme = "dark";
- if(process.browser) {
- if(uid && rootGetters.userSettings) {
- theme = (rootGetters.userSettings.general && rootGetters.userSettings.general.theme) ? rootGetters.userSettings.general.theme : theme;
+ if (process.browser) {
+ if (uid && rootGetters.userSettings) {
+ theme =
+ rootGetters.userSettings.general && rootGetters.userSettings.general.theme
+ ? rootGetters.userSettings.general.theme
+ : theme;
} else {
- theme = (localStorage.getItem("theme")) ? localStorage.getItem("theme") : theme;
+ theme = localStorage.getItem("theme") ? localStorage.getItem("theme") : theme;
}
document.documentElement.setAttribute("data-theme", theme);
}
commit("SET_THEME", theme);
- }
+ }
// Set the new choosen theme
else {
- if(theme !== state.theme) {
- if(uid) {
- dispatch("update_settings", {
+ if (theme !== state.theme) {
+ if (uid) {
+ dispatch("update_settings", {
category: "general",
type: "theme",
- value: theme
+ value: theme,
});
} else {
localStorage.setItem("theme", theme);
@@ -129,15 +149,15 @@ export default {
setActionRoll({ commit, state }, newRoll) {
let current = state.action_rolls;
let key = Date.now() + Math.random().toString(36).substring(7);
-
+
// Shuffle the key
- key = key.toString().split('');
+ key = key.toString().split("");
key.sort(() => {
return 0.5 - Math.random();
- });
- key = key.join('');
+ });
+ key = key.join("");
Vue.set(newRoll, "key", key);
-
+
current.unshift(newRoll);
commit("SET_ACTION_ROLLS", current);
},
@@ -147,48 +167,73 @@ export default {
setSlide({ commit, state }, payload) {
let slide = state.slide;
- if(slide.type !== payload.type || (JSON.stringify(slide.data) !== JSON.stringify(payload.data) && payload.data != undefined)) {
- commit('SET_SLIDE', false);
- setTimeout(() => commit('SET_SLIDE', payload), 100);
+ if (
+ slide.type !== payload.type ||
+ (JSON.stringify(slide.data) !== JSON.stringify(payload.data) && payload.data != undefined)
+ ) {
+ commit("SET_SLIDE", false);
+ setTimeout(() => commit("SET_SLIDE", payload), 100);
} else {
- commit('SET_SLIDE', false);
- }
+ commit("SET_SLIDE", false);
+ }
},
toggleSideCollapsed({ commit, state, dispatch }) {
commit("TOGGLE_SIDE_COLLAPSE"); // First toggle
const collapsed = state.side_collapsed; // Then get from state
-
+
// Then update in user settings
- dispatch("update_settings", {
+ dispatch("update_settings", {
category: "general",
type: "side_collapsed",
- value: collapsed || undefined
+ value: collapsed || undefined,
});
},
setSideCollapsed({ commit, rootGetters }) {
const uid = rootGetters.user ? rootGetters.user.uid : undefined;
- if(uid && rootGetters.userSettings) {
- const collapsed = (rootGetters.userSettings.general && rootGetters.userSettings.general.side_collapsed)
- ? rootGetters.userSettings.general.side_collapsed : false;
+ if (uid && rootGetters.userSettings) {
+ const collapsed =
+ rootGetters.userSettings.general && rootGetters.userSettings.general.side_collapsed
+ ? rootGetters.userSettings.general.side_collapsed
+ : false;
commit("SET_SIDE_COLLAPSE", collapsed);
}
},
setSideSmallScreen({ commit }, payload) {
- commit("SET_SIDE_SMALL_SCREEN", payload)
+ commit("SET_SIDE_SMALL_SCREEN", payload);
},
},
mutations: {
- SET_INITIALIZED(state, payload) { Vue.set(state, "initialized", payload) },
- SET_THEME(state, payload) { Vue.set(state, 'theme', payload); },
- SET_SLIDE(state, payload) { Vue.set(state, 'slide', payload); },
- SET_ROLLS(state, payload) { Vue.set(state, 'rolls', payload); },
- SET_ACTION_ROLLS(state, payload) { Vue.set(state, 'action_rolls', payload); },
- CLEAR_ACTION_ROLLS(state) { Vue.set(state, 'action_rolls', []); },
- REMOVE_ACTION_ROLL(state, payload) { Vue.delete(state.action_rolls, payload); },
- TOGGLE_SIDE_COLLAPSE(state) { Vue.set(state, 'side_collapsed', !state.side_collapsed); },
- SET_SIDE_COLLAPSE(state, payload) { Vue.set(state, 'side_collapsed', payload) },
- SET_SIDE_SMALL_SCREEN(state, payload) { Vue.set(state, 'side_small_screen', payload); },
- }
-};
\ No newline at end of file
+ SET_INITIALIZED(state, payload) {
+ Vue.set(state, "initialized", payload);
+ },
+ SET_THEME(state, payload) {
+ Vue.set(state, "theme", payload);
+ },
+ SET_SLIDE(state, payload) {
+ Vue.set(state, "slide", payload);
+ },
+ SET_ROLLS(state, payload) {
+ Vue.set(state, "rolls", payload);
+ },
+ SET_ACTION_ROLLS(state, payload) {
+ Vue.set(state, "action_rolls", payload);
+ },
+ CLEAR_ACTION_ROLLS(state) {
+ Vue.set(state, "action_rolls", []);
+ },
+ REMOVE_ACTION_ROLL(state, payload) {
+ Vue.delete(state.action_rolls, payload);
+ },
+ TOGGLE_SIDE_COLLAPSE(state) {
+ Vue.set(state, "side_collapsed", !state.side_collapsed);
+ },
+ SET_SIDE_COLLAPSE(state, payload) {
+ Vue.set(state, "side_collapsed", payload);
+ },
+ SET_SIDE_SMALL_SCREEN(state, payload) {
+ Vue.set(state, "side_small_screen", payload);
+ },
+ },
+};
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index a9d38f372..4e69c8530 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -1,13 +1,12 @@
-import { Cookies } from 'quasar';
-import { db, auth } from 'src/firebase';
+import { Cookies } from "quasar";
+import { db, auth } from "src/firebase";
import { userServices } from "src/services/user";
-import { voucherService } from 'src/services/vouchers';
-import { serverUtils } from 'src/services/serverUtils'
+import { voucherService } from "src/services/vouchers";
-import Vue from 'vue';
+import Vue from "vue";
-const users_ref = db.ref('users');
-const tiers_ref = db.ref('tiers');
+const users_ref = db.ref("users");
+const tiers_ref = db.ref("tiers");
const user_state = () => ({
user_services: null,
@@ -21,28 +20,52 @@ const user_state = () => ({
userSettings: {},
poster: undefined,
broadcast: {},
- followed: {}
+ followed: {},
});
const user_getters = {
- user_services: (state) => { return state.user_services },
- user: function( state ) { return state.user; },
- userInfo(state) { return state.userInfo; },
- userSettings(state) { return state.userSettings; },
- tier(state) { return state.tier; },
- voucher(state) { return state.voucher;},
- overencumbered(state) { return state.overencumbered; },
- content_count(state) { return state.content_count; },
- slots_used(state) { return state.slots_used; },
- poster(state) { return state.poster; },
- broadcast(state) { return state.broadcast; },
- followed(state) { return state.followed; },
+ user_services: (state) => {
+ return state.user_services;
+ },
+ user: function (state) {
+ return state.user;
+ },
+ userInfo(state) {
+ return state.userInfo;
+ },
+ userSettings(state) {
+ return state.userSettings;
+ },
+ tier(state) {
+ return state.tier;
+ },
+ voucher(state) {
+ return state.voucher;
+ },
+ overencumbered(state) {
+ return state.overencumbered;
+ },
+ content_count(state) {
+ return state.content_count;
+ },
+ slots_used(state) {
+ return state.slots_used;
+ },
+ poster(state) {
+ return state.poster;
+ },
+ broadcast(state) {
+ return state.broadcast;
+ },
+ followed(state) {
+ return state.followed;
+ },
};
const user_actions = {
async get_user_services({ getters, commit }) {
if (getters.user_services === null || !Object.keys(getters.user_services).length) {
- commit("SET_USER_SERVICES", new userServices);
+ commit("SET_USER_SERVICES", new userServices());
}
return getters.user_services;
},
@@ -51,36 +74,42 @@ const user_actions = {
commit("SET_USER", user);
},
async setUserInfo({ commit, dispatch, rootGetters }) {
- if(rootGetters.user) {
+ if (rootGetters.user) {
const user = users_ref.child(rootGetters.user.uid);
- user.on("value", async user_snapshot => {
+ user.on("value", async (user_snapshot) => {
const user_info = user_snapshot.val();
- if(user_info) {
+ if (user_info) {
//Fetch patron info with email
- const email = (user_info.patreon_email) ? user_info.patreon_email.toLowerCase() : user_info.email.toLowerCase();
+ const email = user_info.patreon_email
+ ? user_info.patreon_email.toLowerCase()
+ : user_info.email.toLowerCase();
// User always basic reward tier
let path = `tiers/basic`;
// Use firebase serverTimeOffset to get the date from the server and not the client.
// https://firebase.google.com/docs/database/web/offline-capabilities#clock-skew
- let time_ms = 0
- await db.ref("/.info/serverTimeOffset")
+ let time_ms = 0;
+ await db
+ .ref("/.info/serverTimeOffset")
.once("value")
- .then(function stv(data) {
- time_ms = data.val() + Date.now();
- }, (err) => {
- return err;
- });
+ .then(
+ function stv(data) {
+ time_ms = data.val() + Date.now();
+ },
+ (err) => {
+ return err;
+ }
+ );
const server_time = new Date(time_ms).toISOString();
// If user has voucher use this
- if (user_info.voucher){
- let voucher = user_info.voucher
+ if (user_info.voucher) {
+ let voucher = user_info.voucher;
- if (user_info.voucher.date === undefined){
+ if (user_info.voucher.date === undefined) {
path = `tiers/${user_info.voucher.id}`;
} else {
const end_date = new Date(user_info.voucher.date).toISOString();
@@ -95,21 +124,23 @@ const user_actions = {
commit("SET_VOUCHER", voucher);
}
let vouch_tiers = db.ref(path);
- vouch_tiers.once("value", voucher_snap => {
+ vouch_tiers.once("value", (voucher_snap) => {
// Get the order of voucher/basic
let voucher_order = voucher_snap.val().order;
// Search email in patrons
- let patrons = db.ref("new_patrons").orderByChild("email").equalTo(email)
- patrons.on("value" , async patron_snapshot => {
+ let patrons = db.ref("new_patrons").orderByChild("email").equalTo(email);
+ patrons.on("value", async (patron_snapshot) => {
// If user patron check if patron tier is higher than voucher/basic tier
- if(patron_snapshot.val()) {
+ if (patron_snapshot.val()) {
const patron_data = Object.values(patron_snapshot.val())[0];
// Set pledge to expired if there is no pledge_end present
const expired = new Date(time_ms);
expired.setDate(expired.getDate() - 1);
- const pledge_end = patron_data.pledge_end ? new Date(patron_data.pledge_end).toISOString() : expired.toISOString();
+ const pledge_end = patron_data.pledge_end
+ ? new Date(patron_data.pledge_end).toISOString()
+ : expired.toISOString();
// Compare patron tiers to find highest tier checking order in FB
let highest_order = 0;
@@ -117,20 +148,20 @@ const user_actions = {
// When the last_charge_status = Pending a user won't have a Patreon tier yet
// Just hand out free tier for pending status
- if(patron_data.tiers) {
+ if (patron_data.tiers) {
const patron_tierlist = Object.keys(patron_data.tiers);
if (patron_tierlist.length > 1) {
for (let i in patron_tierlist) {
- let tier_id = patron_tierlist[i]
+ let tier_id = patron_tierlist[i];
// SMART AWAIT ASYNC CONSTRUCTION #bless Key
- await tiers_ref.child(tier_id).once("value", tier_snapshot => {
- let tier_order = tier_snapshot.val().order
+ await tiers_ref.child(tier_id).once("value", (tier_snapshot) => {
+ let tier_order = tier_snapshot.val().order;
if (tier_order > highest_order) {
highest_order = tier_order;
highest_tier = tier_id;
}
- })
+ });
}
} else {
highest_tier = patron_tierlist[0];
@@ -139,12 +170,12 @@ const user_actions = {
//Get tier info
let patron_tier = db.ref(`tiers/${highest_tier}`);
- patron_tier.on("value" , tier_snapshot => {
+ patron_tier.on("value", (tier_snapshot) => {
//Save Patron info under UserInfo
user_info.patron = {
last_charge_status: patron_data.last_charge_status,
pledge_end,
- tier: tier_snapshot.val().name
+ tier: tier_snapshot.val().name,
};
if (tier_snapshot.val().order >= voucher_order && pledge_end >= server_time) {
@@ -168,18 +199,18 @@ const user_actions = {
// Return a promise, so you can wait for it in the initialize function from store/general.js
return new Promise((resolve) => {
setTimeout(() => {
- resolve()
- }, 1000)
+ resolve();
+ }, 1000);
});
},
async set_user_settings({ commit, dispatch, rootGetters }) {
const uid = rootGetters.user.uid;
- if(uid) {
+ if (uid) {
const services = await dispatch("get_user_services");
try {
const user_settings = await services.getSettings(uid);
- commit('SET_USER_SETTINGS', user_settings || {});
- } catch(error) {
+ commit("SET_USER_SETTINGS", user_settings || {});
+ } catch (error) {
throw error;
}
}
@@ -193,12 +224,12 @@ const user_actions = {
},
setPoster({ state }) {
- db.ref('posters').once('value', snapshot => {
+ db.ref("posters").once("value", (snapshot) => {
let count = snapshot.val();
const new_count = count + 1;
- db.ref('posters').set(new_count);
+ db.ref("posters").set(new_count);
state.poster = true;
- })
+ });
},
async checkEncumbrance({ state, commit, rootGetters }) {
let count = {};
@@ -210,6 +241,7 @@ const user_actions = {
count.npcs = rootGetters["npcs/npc_count"];
count.items = rootGetters["items/item_count"];
count.reminders = rootGetters["reminders/reminder_count"];
+ count.spells = rootGetters["spells/spell_count"];
count.encounters = 0;
let used_slots = Object.values(count).reduce((sum, count) => sum + count, 0);
@@ -224,78 +256,79 @@ const user_actions = {
}
}
- if(state.tier) {
+ if (state.tier) {
let benefits = state.tier.benefits;
let available_slots = Object.values(benefits).reduce((sum, count) => sum + count, 0);
// Add encounter slots for every campaign above 1
const more_encounters = benefits.campaigns - 1;
- if(more_encounters) {
- for(let i = 1; i <= more_encounters; i++) {
+ if (more_encounters) {
+ for (let i = 1; i <= more_encounters; i++) {
available_slots = available_slots + benefits.encounters;
}
}
// Check overencumbrance
- overencumbered = (count.campaigns > benefits.campaigns ||
+ overencumbered =
+ count.campaigns > benefits.campaigns ||
count.encounters > benefits.encounters ||
count.npcs > benefits.npcs ||
count.items > benefits.items ||
count.reminders > benefits.reminders ||
count.players > benefits.players ||
- count.characters > benefits.characters
- );
+ count.spells > benefits.spells ||
+ count.characters > benefits.characters;
commit("SET_SLOTS_USED", { available_slots, used_slots });
}
commit("SET_CONTENT_COUNT", count);
commit("SET_ENCUMBRANCE", overencumbered);
},
- setLive({state, rootGetters, commit}, { campaign_id, encounter_id, shares }) {
- if(state.broadcast.live === campaign_id) {
+ setLive({ state, rootGetters, commit }, { campaign_id, encounter_id, shares }) {
+ if (state.broadcast.live === campaign_id) {
db.ref(`broadcast/${rootGetters.user.uid}`).remove();
commit("SET_BROADCAST", {});
} else {
let broadcast = { live: campaign_id, shares };
- if(encounter_id) broadcast.encounter = encounter_id;
+ if (encounter_id) broadcast.encounter = encounter_id;
db.ref(`broadcast/${rootGetters.user.uid}`).set(broadcast);
commit("SET_BROADCAST", broadcast);
}
},
- setLiveEncounter({rootGetters, commit}, encounter_id) {
- const encounter = (encounter_id) ? encounter_id : false;
+ setLiveEncounter({ rootGetters, commit }, encounter_id) {
+ const encounter = encounter_id ? encounter_id : false;
const uid = rootGetters.user ? rootGetters.user.uid : undefined;
- if(uid) {
+ if (uid) {
db.ref(`broadcast/${uid}/encounter`).set(encounter);
commit("SET_BROADCAST_ENCOUNTER", encounter);
}
},
- setLiveShares({state, commit}, shares) {
- if(state.broadcast && state.broadcast.live) {
+ setLiveShares({ state, commit }, shares) {
+ if (state.broadcast && state.broadcast.live) {
commit("SET_BROADCAST_SHARES", shares);
}
},
async get_followed({ state, commit, dispatch }) {
- const follow = (state.userInfo) ? state.userInfo.follow : undefined;
- let followed = (state.followed) ? state.followed : undefined;
+ const follow = state.userInfo ? state.userInfo.follow : undefined;
+ let followed = state.followed ? state.followed : undefined;
- if(!followed || !Object.keys(followed).length) {
- const services = await dispatch("get_user_services");
- try {
+ if (!followed || !Object.keys(followed).length) {
+ const services = await dispatch("get_user_services");
+ try {
followed = {};
- for(const uid in follow) {
+ for (const uid in follow) {
const user = await services.getSearchUser(uid);
- if(user) {
+ if (user) {
followed[uid] = user.username;
}
}
- commit("SET_FOLLOWED", followed);
- } catch(error) {
- throw error;
- }
- }
- return followed;
+ commit("SET_FOLLOWED", followed);
+ } catch (error) {
+ throw error;
+ }
+ }
+ return followed;
},
/**
@@ -306,17 +339,20 @@ const user_actions = {
* @param {string} type
* @param {any} value
*/
- async update_settings({ commit, dispatch, rootGetters }, {category, sub_category, type, value}) {
+ async update_settings(
+ { commit, dispatch, rootGetters },
+ { category, sub_category, type, value }
+ ) {
const uid = rootGetters.user.uid;
- if(uid) {
+ if (uid) {
const services = await dispatch("get_user_services");
try {
await services.updateSettings(uid, category, sub_category, type, value);
- commit('UPDATE_USER_SETTINGS', { category, sub_category, type, value });
+ commit("UPDATE_USER_SETTINGS", { category, sub_category, type, value });
// The sidebar collapse is stored in a variable in the general store
- if(category === "general") dispatch("setSideCollapsed");
- } catch(error) {
+ if (category === "general") dispatch("setSideCollapsed");
+ } catch (error) {
throw error;
}
}
@@ -331,22 +367,22 @@ const user_actions = {
*/
async set_default_settings({ commit, dispatch, rootGetters }, category) {
const uid = rootGetters.user.uid;
- if(uid) {
+ if (uid) {
const services = await dispatch("get_user_services");
try {
await services.setDefaultSettings(uid, category);
- commit('SET_DEFAULT_SETTINGS', category);
+ commit("SET_DEFAULT_SETTINGS", category);
// The sidebar collapse is stored in a variable in the general store
- if(category === "general") dispatch("setSideCollapsed");
- } catch(error) {
+ if (category === "general") dispatch("setSideCollapsed");
+ } catch (error) {
throw error;
}
}
},
// Signs out the user and cleares all content stores.
- async sign_out({commit, dispatch}) {
+ async sign_out({ commit, dispatch }) {
// Clear content stores
await dispatch("campaigns/clear_campaign_store", {}, { root: true });
await dispatch("encounters/clear_encounter_store", {}, { root: true });
@@ -354,63 +390,76 @@ const user_actions = {
await dispatch("npcs/clear_npc_store", {}, { root: true });
await dispatch("reminders/clear_reminder_store", {}, { root: true });
await dispatch("items/clear_item_store", {}, { root: true });
+ await dispatch("items/clear_spell_store", {}, { root: true });
await commit("CLEAR_USER", undefined);
// Sign out from firebase
- Cookies.remove("access_token");
+ Cookies.remove("access_token", { path: "/" });
await auth.signOut();
},
- async remove_voucher( { rootGetters }) {
- userServices.removeVoucher(rootGetters.user.uid).then(() => {
- return;
- }).catch((error) => {
- throw error;
- })
- },
-
- async set_active_voucher({ commit, dispatch, rootGetters }, voucher_string) {
- const voucher = await this.dispatch("get_valid_voucher_by_string", voucher_string);
-
- if (voucher) {
- const voucher_tier = (await db.ref(`tiers/${voucher.tier}`).once('value')).val();
- const current_tier = rootGetters.tier
- if (voucher_tier.order > current_tier.order) {
-
- return userServices.setActiveVoucher(rootGetters.user.uid, voucher)
- .then(async ({fbVoucher}) => {
- commit("SET_TIER", voucher_tier)
- commit("SET_VOUCHER", fbVoucher);
- }).catch(error => {
- throw error;
- })
- }
- throw "Voucher is lower than current subscription tier."
- }
- throw "No valid voucher found.";
- },
-
- async get_valid_voucher_by_string({ commit, dispatch }, voucher_string) {
- const vouchers = await voucherService.getValidVouchers();
- const intersection = vouchers.filter(v => v.voucher == voucher_string.toUpperCase());
- return intersection.length ? intersection[0] : false;
- },
+ async remove_voucher({ rootGetters }) {
+ userServices
+ .removeVoucher(rootGetters.user.uid)
+ .then(() => {
+ return;
+ })
+ .catch((error) => {
+ throw error;
+ });
+ },
+
+ async set_active_voucher({ commit, dispatch, rootGetters }, voucher_string) {
+ const voucher = await this.dispatch("get_valid_voucher_by_string", voucher_string);
+
+ if (voucher) {
+ const voucher_tier = (await db.ref(`tiers/${voucher.tier}`).once("value")).val();
+ const current_tier = rootGetters.tier;
+ if (voucher_tier.order > current_tier.order) {
+ return userServices
+ .setActiveVoucher(rootGetters.user.uid, voucher)
+ .then(async ({ fbVoucher }) => {
+ commit("SET_TIER", voucher_tier);
+ commit("SET_VOUCHER", fbVoucher);
+ })
+ .catch((error) => {
+ throw error;
+ });
+ }
+ throw "Voucher is lower than current subscription tier.";
+ }
+ throw "No valid voucher found.";
+ },
+
+ async get_valid_voucher_by_string({ commit, dispatch }, voucher_string) {
+ const vouchers = await voucherService.getValidVouchers();
+ const intersection = vouchers.filter((v) => v.voucher == voucher_string.toUpperCase());
+ return intersection.length ? intersection[0] : false;
+ },
};
-const user_mutations = {
- SET_USER_SERVICES(state, payload) { Vue.set(state, "user_services", payload); },
- SET_USER(state, payload) { Vue.set(state, "user", payload); },
- SET_USERINFO(state, payload) { Vue.set(state, "userInfo", payload); },
- SET_USER_SETTINGS(state, payload) { Vue.set(state, "userSettings", payload); },
+const user_mutations = {
+ SET_USER_SERVICES(state, payload) {
+ Vue.set(state, "user_services", payload);
+ },
+ SET_USER(state, payload) {
+ Vue.set(state, "user", payload);
+ },
+ SET_USERINFO(state, payload) {
+ Vue.set(state, "userInfo", payload);
+ },
+ SET_USER_SETTINGS(state, payload) {
+ Vue.set(state, "userSettings", payload);
+ },
UPDATE_USER_SETTINGS(state, { category, sub_category, type, value }) {
- if(!sub_category) {
- if(state.userSettings && state.userSettings[category]) {
+ if (!sub_category) {
+ if (state.userSettings && state.userSettings[category]) {
Vue.set(state.userSettings[category], type, value);
} else {
Vue.set(state.userSettings, category, { [type]: value });
}
- } else if(state.userSettings && state.userSettings[category]) {
- if(state.userSettings[category][sub_category]) {
+ } else if (state.userSettings && state.userSettings[category]) {
+ if (state.userSettings[category][sub_category]) {
Vue.set(state.userSettings[category][sub_category], type, value);
} else {
Vue.set(state.userSettings[category], sub_category, { [type]: value });
@@ -419,18 +468,36 @@ const user_mutations = {
Vue.set(state.userSettings, category, { [sub_category]: { [type]: value } });
}
},
- SET_DEFAULT_SETTINGS(state, category) { Vue.delete(state.userSettings, category); },
- SET_TIER(state, payload) { Vue.set(state, "tier", payload); },
- SET_VOUCHER(state, payload) { Vue.set(state, "voucher", payload); },
- SET_ENCUMBRANCE(state, value) { Vue.set(state, "overencumbered", value); },
- SET_CONTENT_COUNT(state, value) { Vue.set(state, "content_count", value); },
+ SET_DEFAULT_SETTINGS(state, category) {
+ Vue.delete(state.userSettings, category);
+ },
+ SET_TIER(state, payload) {
+ Vue.set(state, "tier", payload);
+ },
+ SET_VOUCHER(state, payload) {
+ Vue.set(state, "voucher", payload);
+ },
+ SET_ENCUMBRANCE(state, value) {
+ Vue.set(state, "overencumbered", value);
+ },
+ SET_CONTENT_COUNT(state, value) {
+ Vue.set(state, "content_count", value);
+ },
SET_SLOTS_USED(state, { available_slots, used_slots }) {
Vue.set(state, "slots_used", { available_slots, used_slots });
},
- SET_FOLLOWED(state, payload) { Vue.set(state, "followed", payload); },
- SET_BROADCAST(state, payload) { Vue.set(state, "broadcast", payload) },
- SET_BROADCAST_ENCOUNTER(state, payload) { Vue.set(state.broadcast, "encounter", payload) },
- SET_BROADCAST_SHARES(state, payload) { Vue.set(state.broadcast, "shares", payload) },
+ SET_FOLLOWED(state, payload) {
+ Vue.set(state, "followed", payload);
+ },
+ SET_BROADCAST(state, payload) {
+ Vue.set(state, "broadcast", payload);
+ },
+ SET_BROADCAST_ENCOUNTER(state, payload) {
+ Vue.set(state.broadcast, "encounter", payload);
+ },
+ SET_BROADCAST_SHARES(state, payload) {
+ Vue.set(state.broadcast, "shares", payload);
+ },
CLEAR_USER(state) {
Vue.set(state, "user", undefined);
Vue.set(state, "userInfo", undefined);
@@ -442,12 +509,12 @@ const user_mutations = {
Vue.set(state, "userSettings", {});
Vue.set(state, "poster", undefined);
Vue.set(state, "broadcast", {});
- }
+ },
};
export default {
- state: user_state,
- getters: user_getters,
- actions: user_actions,
- mutations: user_mutations
-}
+ state: user_state,
+ getters: user_getters,
+ actions: user_actions,
+ mutations: user_mutations,
+};
diff --git a/src/store/modules/userContent/campaigns.js b/src/store/modules/userContent/campaigns.js
index c9a67a285..0390a02e9 100644
--- a/src/store/modules/userContent/campaigns.js
+++ b/src/store/modules/userContent/campaigns.js
@@ -1,872 +1,920 @@
-import Vue from 'vue';
-import { campaignServices } from "src/services/campaigns";
-import _ from 'lodash';
+import Vue from "vue";
+import { campaignServices } from "src/services/campaigns";
+import _ from "lodash";
// Converts a full campaign to a search_campaign
const convert_campaign = (campaign) => {
const properties = [
- "name",
- "background",
- "hk_background",
- "player_count",
- "advancement",
- "timestamp",
- "private"
+ "name",
+ "background",
+ "hk_background",
+ "player_count",
+ "advancement",
+ "timestamp",
+ "private",
];
- const returnCampaign = {};
-
- for(const prop of properties) {
- if(campaign.hasOwnProperty(prop)) {
- returnCampaign[prop] = campaign[prop];
- }
+ const returnCampaign = {};
+
+ for (const prop of properties) {
+ if (campaign.hasOwnProperty(prop)) {
+ returnCampaign[prop] = campaign[prop];
+ }
}
return returnCampaign;
-}
+};
const campaign_state = () => ({
- campaign_services: null,
- active_campaign: undefined,
- cached_campaigns: {},
- campaigns: undefined,
- campaign_count: 0
+ campaign_services: null,
+ active_campaign: undefined,
+ cached_campaigns: {},
+ campaigns: undefined,
+ campaign_count: 0,
});
const campaign_getters = {
- campaigns: (state) => {
- // Convert object to sorted array
- return _.chain(state.campaigns)
- .filter((campaign, key) => {
- campaign.key = key;
- return campaign;
- }).orderBy((campaign) => {
- return parseInt(campaign.timestamp)
- } , 'asc')
- .value();
- },
- campaign_count: (state) => {
- return state.campaign_count;
- },
- campaign_services: (state) => { return state.campaign_services; }
+ campaigns: (state) => {
+ // Convert object to sorted array
+ return _.chain(state.campaigns)
+ .filter((campaign, key) => {
+ campaign.key = key;
+ return campaign;
+ })
+ .orderBy((campaign) => {
+ return parseInt(campaign.timestamp);
+ }, "asc")
+ .value();
+ },
+ campaign_count: (state) => {
+ return state.campaign_count;
+ },
+ campaign_services: (state) => {
+ return state.campaign_services;
+ },
};
const campaign_actions = {
- async get_campaign_services({ getters, commit }) {
- if(getters.campaign_services === null || !Object.keys(getters.campaign_services).length) {
- commit("SET_CAMPAIGN_SERVICES", new campaignServices);
- }
- return getters.campaign_services;
- },
-
- /**
- * Fetches all the search_campaigns for a user
- * and stores them in campaigns
- */
- async get_campaigns({ state, rootGetters, dispatch, commit }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- let campaigns = (state.campaigns) ? state.campaigns : undefined;
-
- if(!campaigns && uid) {
- const services = await dispatch("get_campaign_services");
- try {
- campaigns = await services.getCampaigns(uid);
- commit("SET_CAMPAIGNS", campaigns || {});
- } catch(error) {
- throw error;
- }
- }
- return campaigns;
- },
-
- /**
- * Fetches the total count of campaigns for a user
- * and stores it in campaign_count
- */
- async fetch_campaign_count({ rootGetters, commit, dispatch }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- const count = await services.getCampaignCount(uid) || 0;
- commit("SET_CAMPAIGN_COUNT", count);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Get a single campaign
- * - first try to find it in the store, then fetch if wasn't present
- * - Check advancement
- * - Set current hit points
- * - Remove ghost players
- * - Remove ghost companions
- *
- * @param {string} uid userId
- * @param {string} id campaignId
- */
- async get_campaign({ state, commit, dispatch }, { uid, id }) {
- let campaign = (state.cached_campaigns[uid]) ? state.cached_campaigns[uid][id] : undefined;
- const services = await dispatch("get_campaign_services");
-
- // The campaign is not in the store and needs to be fetched from the database
- if(!campaign) {
- try {
- campaign = await services.getCampaign(uid, id);
- commit("SET_CACHED_CAMPAIGN", { uid, id, campaign });
- } catch(error) {
- throw error;
- }
- }
-
- // Check advancement
- if(!campaign.advancement) {
- await dispatch(
- "set_campaign_prop",
- { id, property: "advancement", value: "milestone"}
- );
- }
-
- // Create a list with all companion ids
- let player_companions = [];
-
- // Remove ghost players
- if(campaign.players) {
- for(const [playerId, campaign_player] of Object.entries(campaign.players)) {
- const player = await dispatch("players/get_player", { uid, id: playerId}, { root: true });
-
- if(!player) {
- await dispatch("delete_player", { id, player: { "key": playerId }});
- console.warn(`Ghost player ${playerId} deleted`);
- } else {
- // If the player has no curHp, set it
- if(campaign_player.curHp === undefined) {
- await dispatch(
- "update_campaign_entity",
- { uid, campaignId: id, type: "players", id: playerId, property: "curHp", value: player.maxHp }
- );
- }
-
- // Save companions in list
- if(player.companions) {
- player_companions = player_companions.concat(Object.keys(player.companions));
- }
- }
- }
- }
-
- // Remove ghost companions
- if(campaign.companions) {
- for(const [companionId, campaign_companion] of Object.entries(campaign.companions)) {
- const companion = await dispatch("npcs/get_npc", { uid, id: companionId }, { root: true });
-
- // Delete companion if:
- // - the NPC doesn't exit
- // - there are no players with companions in this campaign
- // - there is no player with this NPC as companion in the campaign
- if(!companion || !player_companions.length || !player_companions.includes(companionId)) {
- await dispatch("delete_companion", { id, companionId });
- console.warn(`Ghost companion ${companionId} deleted from campaign`);
- } else {
- // If the companion has no curHp, set it
- if(campaign_companion.curHp === undefined) {
- await dispatch(
- "update_campaign_entity",
- { uid, campaignId: id, type: "companions", id: companionId, property: "curHp", value: companion.hit_points }
- );
- }
- }
- }
- }
-
- // Remove ghost items
- if(campaign.inventory && campaign.inventory.items) {
- for(const [item_id, item] of Object.entries(campaign.inventory.items)) {
- if(item.linked_item && item.linked_item.custom) {
- const linked_item = await dispatch("items/get_item", { uid, id: item.linked_item.key }, { root: true });
-
- // If the item doesn't exist, remove the item link
- if(!linked_item) {
- await services.updateCampaign(uid, id, `/inventory/items/${item_id}`, { linked_item: null });
- delete item.linked_item;
- console.warn(`Ghost item link deleted from ${item.public_name}`);
- }
- }
- }
- }
-
- return campaign;
- },
-
- /**
- * Sets a campaign as active
- * used to display at the top of the user content overview /content
- *
- * @param {string} id
- */
- async set_active_campaign({ commit, dispatch, rootGetters }, id) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.setActiveCampaign(uid, id);
- commit("SET_ACTIVE_CAMPAIGN", id);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Adds a newly created campaign for a user
- * A user can only add campaigns for themselves so we use the uid from the store
- *
- * @param {object} campaign
- * @returns {string} the id of the newly added campaign
- */
- async add_campaign({ rootGetters, commit, dispatch }, campaign) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- const available_slots = rootGetters.tier.benefits.campaigns;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- const used_slots = await services.getCampaignCount(uid);
-
- if(used_slots >= available_slots) {
- throw "Not enough slots";
- }
- try {
- const search_campaign = convert_campaign(campaign);
- const id = await services.addCampaign(uid, campaign, search_campaign);
- commit("SET_CAMPAIGN", { uid, id, search_campaign });
-
- const new_count = await services.updateCampaignCount(uid, 1);
- commit("SET_CAMPAIGN_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return id;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Updates and existing campaign
- * Only updates properties that are present search_campaigns
- *
- * @param {string} uid
- * @param {string} id
- * @param {object} campaign
- */
- async update_campaign({ commit, dispatch }, { uid, id, campaign }) {
- if(uid) {
- try {
- const services = await dispatch("get_campaign_services");
- const search_campaign = convert_campaign(campaign);
- await services.updateCampaign(uid, id, "", search_campaign, true);
-
- // Update all the props in the cached campaign
- for(const [property, value] of Object.entries(campaign)) {
- commit("SET_CACHED_PROP", { uid, id, property, value });
- }
-
- commit("SET_CAMPAIGN", { uid, id, search_campaign });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Deletes a campaign
- * - Deletes all encounters for this campaign
- * - Removes campaign_id from players linked to the campaign
- *
- * @param {object} id
- */
- async delete_campaign({ rootGetters, commit, dispatch }, id) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- const campaign = await dispatch("get_campaign", { uid, id });
- await services.deleteCampaign(uid, id);
-
- // Delete all encounters of the campaign
- dispatch("encounters/delete_campaign_encounters", id, { root: true });
-
- // Remove campaign_id from players linked to the campaign
- for (const playerId in campaign.players) {
- dispatch("players/set_player_prop", { uid, id: playerId, property: "campaign_id", value: null }, { root: true });
- }
-
- commit("REMOVE_CAMPAIGN", id);
- commit("REMOVE_CACHED_CAMPAIGN", { uid, id });
-
- // Update campaign count
- const new_count = await services.updateCampaignCount(uid, -1);
- commit("SET_CAMPAIGN_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Add a new player to the campaign
- *
- * @param {string} playerId
- * @param {object} campaign search_campaign object (needed for campaignId and player_count)
- */
- async add_player({ rootGetters, commit, dispatch }, { playerId, campaign }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- const player = await dispatch("players/get_player", { uid, id: playerId }, { root: true });
- const campaign_player = { curHp: player.maxHp };
-
- // If the campaign has experience advancement
- // make sure the player has experience points set
- if(campaign.advancement === "experience" && player.experience === undefined) {
- await dispatch("players/set_player_prop", {uid, id: playerId, property: "experience", value: 0 }, { root: true });
- }
-
- // Set the campaign_id on the player
- await dispatch("players/set_player_prop", { uid, id: playerId, property: "campaign_id", value: campaign.key }, { root: true })
-
- // Add companions
- if(player.companions) {
- for(const companionId in player.companions) {
- let companion = await dispatch("npcs/get_npc", { uid, id: companionId }, { root: true });
-
- // If the NPC doesn't exist, delete the companion form the player
- if(!companion) {
- await dispatch("players/delete_companion", { uid, playerId, id: companionId }, { root: true });
- } else {
- companion = { curHp: companion.hit_points }; // Only need the hit_points
- // Add the companion to the campaign
- await dispatch("update_companion", {
- uid,
- id: campaign.key,
- companionId,
- property: "curHp",
- value: companion.curHp
- });
- commit("SET_COMPANION", { uid, id: campaign.key, companionId, companion });
- }
- }
- }
-
- await services.setPlayer(uid, campaign.key, playerId, campaign_player);
- commit("SET_PLAYER", { uid, id: campaign.key, playerId, player });
-
- const new_count = await services.updatePlayerCount(uid, campaign.key, 1);
- commit("SET_PLAYER_COUNT", { uid, id: campaign.key, new_count });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Updates the player_count of a campaign
- *
- * @param {string} id campaignId
- * @param {number} new_count
- */
- async update_player_count({ rootGetters, commit, dispatch }, { id, new_count }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.updateSearchCampaign(uid, id, "", { "player_count": new_count });
- commit("SET_PLAYER_COUNT", { uid, id, new_count });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Overwrites an existing player with a new object
- *
- * @param {string} id campaignId
- * @param {string} playerId
- * @param {object} player
- */
- async edit_campaign_player({ rootGetters, commit, dispatch }, { id, playerId, player }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.setPlayer(uid, id, playerId, player);
- commit("SET_PLAYER", { uid, id, playerId, player });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Updates a single property for a player or compantion
- *
- * @param {string} uid
- * @param {string} campaignId
- * @param {string} type Entity type players/companions
- * @param {string} id entityId
- * @param {string} property
- * @param {string|number|boolean} value
- */
- async update_campaign_entity({ commit, dispatch }, { uid, campaignId, type, id, property, value }) {
- if(uid) {
- const services = await dispatch("get_campaign_services");
- const path = `/${type}/${id}`;
- try {
- await services.updateCampaign(
- uid, campaignId, path, { [property]: value }
- );
- commit("UPDATE_CAMPAIGN_ENTITY", { uid, campaignId, type, id, property, value });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Updates a transformed player or companion
- *
- * @param {string} uid
- * @param {string} campaignId
- * @param {string} type Entity type players/companions
- * @param {string} id entityId
- * @param {string} property
- * @param {number} value
- */
- async update_transformed_entity({ commit, dispatch }, { uid, campaignId, type, id, property, value }) {
- if(uid) {
- const services = await dispatch("get_campaign_services");
- const path = `/${type}/${id}/transformed`;
- try {
- await services.updateCampaign(
- uid, campaignId, path, { [property]: value }
- );
- commit("UPDATE_TRANSFORMED_ENTITY", { uid, campaignId, type, id, property, value });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Updates a companion in a campaign
- * Can also be used to add the companion to the campaign
- *
- * @param {string} id campaignId
- * @param {string} companionId
- * @param {string} property
- * @param {string|number} value
- */
- async update_companion({ dispatch }, { uid, id, companionId, property, value }) {
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- const path = `/companions/${companionId}`;
- await services.updateCampaign(uid, id, path, { [property]: value });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Deletes a companion from a campaign
- *
- * @param {string} id campaignId
- * @param {string} companionId
- */
- async delete_companion({ rootGetters, commit, dispatch }, { id, companionId }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.deleteCompanion(uid, id, companionId);
- commit("DELETE_COMPANION", { uid, id, companionId });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Deletes a player from a campaign
- * - Remove 1 from player_count
- * - Remove the campaign_id from the player
- * - Delete the companions of the player from the campaign
- *
- * When the delete_player function is called during get_campaign,
- * it is possible the search_campaigns aren't fetched
- * in this case the player_count can't be found in the store, but it's sent from the get_campaign function.
- *
- * @param {object} campaign
- * @param {number} player_count
- * @returns {string} the id of the newly added campaign
- */
- async delete_player({ state, rootGetters, commit, dispatch }, { id, player }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- const campaign = (state.campaigns) ? state.campaigns[id] : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- // Delete campaign_id
- await dispatch("players/set_player_prop", { uid, id: player.key, property: "campaign_id", value: null }, { root: true })
-
- // Delete companions
- if(player.companions) {
- for(const companionId of player.companions) {
- await dispatch("delete_companion", { id, companionId });
- }
- }
- await services.deletePlayer(uid, id, player.key);
- commit("DELETE_PLAYER", { uid, id, playerId: player.key });
-
- const new_count = await services.updatePlayerCount(uid, campaign.key, -1);
- commit("SET_PLAYER_COUNT", { uid, id: campaign.key, new_count });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- async set_death_save({ dispatch, commit, rootGetters }, { campaignId, type, id, index, value } ) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- const path = `/${type}/${id}/saves`;
- try {
- await services.updateCampaign(
- uid, campaignId, path, { [index]: value }
- );
- commit("SET_DEATH_SAVE", { uid, campaignId, type, id, index, value });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- async stabilize_entity({ dispatch }, { uid, campaignId, type, id } ) {
- const update = {
- dead: null,
- saves: null,
- stable: true
- }
- for(const [property, value] of Object.entries(update)) {
- dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
- }
- },
-
- async kill_entity({ dispatch }, { uid, campaignId, type, id } ) {
- const update = {
- saves: null,
- stable: null,
- dead: true
- }
- for(const [property, value] of Object.entries(update)) {
- dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
- }
- },
-
- async revive_entity({ dispatch }, { uid, campaignId, type, id, curHp } ) {
- const update = {
- dead: null,
- saves: null,
- stable: null,
- curHp
- };
- for(const [property, value] of Object.entries(update)) {
- dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
- }
- },
-
- /**
- * Updates a non-nested property of a campaign
- *
- * @param {string} id campaignId
- * @param {string} property
- * @param {any} value
- */
- async set_campaign_prop({ rootGetters, commit, dispatch }, { id, property, value }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.updateCampaign(uid, id, "", { [property]: value });
- commit("SET_CACHED_PROP", { uid, id, property, value });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Update the "share" property with the latest value
- * Shares can be seen by players on the public intiative list
- *
- * @param {string} id campaignId
- * @param {object} share Share object
- */
- async set_share({ rootGetters, dispatch }, { id, share }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.setShare(uid, id, share);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Sets the currency in the inventory of a campaign
- *
- * @param {string} id campaignId
- * @param {object} share Share object
- */
- async set_campaign_currency({ rootGetters, commit, dispatch }, { campaignId, value }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.updateCampaign(uid, campaignId, "/inventory", { currency: value });
- commit("SET_CURRENCY", { uid, campaignId, value })
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Adds an item to a campaign inventory
- *
- * @param {string} id campaignId
- * @param {object} share Share object
- */
- async add_campaign_item({ rootGetters, commit, dispatch }, { campaignId, item }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- const id = await services.addItem(uid, campaignId, item);
- commit("SET_ITEM", { uid, campaignId, id, item });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Identifies an item in a campaign, so players can see the linked item.
- *
- * @param {string} id campaignId
- * @param {object} share Share object
- */
- async identify_campaign_item({ rootGetters, commit, dispatch }, { campaignId, id, value }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.updateCampaign(uid, campaignId, `/inventory/items/${id}`, { identified: value });
- commit("IDENTIFY_ITEM", { uid, campaignId, id, value })
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Deletes an item from a campaign inventory
- *
- * @param {string} id campaignId
- * @param {object} share Share object
- */
- async delete_campaign_item({ rootGetters, commit, dispatch }, { campaignId, id }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_campaign_services");
- try {
- await services.updateCampaign(uid, campaignId, `/inventory/items`, { [id]: null });
- commit("DELETE_ITEM", { uid, campaignId, id })
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- clear_campaign_store({ commit, rootGetters }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- commit("CLEAR_STORE");
- }
- }
+ async get_campaign_services({ getters, commit }) {
+ if (getters.campaign_services === null || !Object.keys(getters.campaign_services).length) {
+ commit("SET_CAMPAIGN_SERVICES", new campaignServices());
+ }
+ return getters.campaign_services;
+ },
+
+ /**
+ * Fetches all the search_campaigns for a user
+ * and stores them in campaigns
+ */
+ async get_campaigns({ state, rootGetters, dispatch, commit }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ let campaigns = state.campaigns ? state.campaigns : undefined;
+
+ if (!campaigns && uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ campaigns = await services.getCampaigns(uid);
+ commit("SET_CAMPAIGNS", campaigns || {});
+ } catch (error) {
+ throw error;
+ }
+ }
+ return campaigns;
+ },
+
+ /**
+ * Fetches the total count of campaigns for a user
+ * and stores it in campaign_count
+ */
+ async fetch_campaign_count({ rootGetters, commit, dispatch }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ const count = (await services.getCampaignCount(uid)) || 0;
+ commit("SET_CAMPAIGN_COUNT", count);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Get a single campaign
+ * - first try to find it in the store, then fetch if wasn't present
+ * - Check advancement
+ * - Set current hit points
+ * - Remove ghost players
+ * - Remove ghost companions
+ *
+ * @param {string} uid userId
+ * @param {string} id campaignId
+ */
+ async get_campaign({ state, commit, dispatch }, { uid, id }) {
+ let campaign = state.cached_campaigns[uid] ? state.cached_campaigns[uid][id] : undefined;
+ const services = await dispatch("get_campaign_services");
+
+ // The campaign is not in the store and needs to be fetched from the database
+ if (!campaign) {
+ try {
+ campaign = await services.getCampaign(uid, id);
+ commit("SET_CACHED_CAMPAIGN", { uid, id, campaign });
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ // Check advancement
+ if (!campaign.advancement) {
+ await dispatch("set_campaign_prop", { id, property: "advancement", value: "milestone" });
+ }
+
+ // Create a list with all companion ids
+ let player_companions = [];
+
+ // Remove ghost players
+ if (campaign.players) {
+ for (const [playerId, campaign_player] of Object.entries(campaign.players)) {
+ const player = await dispatch("players/get_player", { uid, id: playerId }, { root: true });
+
+ if (!player) {
+ await dispatch("delete_player", { id, player: { key: playerId } });
+ console.warn(`Ghost player ${playerId} deleted`);
+ } else {
+ // If the player has no curHp, set it
+ if (campaign_player.curHp === undefined) {
+ await dispatch("update_campaign_entity", {
+ uid,
+ campaignId: id,
+ type: "players",
+ id: playerId,
+ property: "curHp",
+ value: player.maxHp,
+ });
+ }
+
+ // Save companions in list
+ if (player.companions) {
+ player_companions = player_companions.concat(Object.keys(player.companions));
+ }
+ }
+ }
+ }
+
+ // Remove ghost companions
+ if (campaign.companions) {
+ for (const [companionId, campaign_companion] of Object.entries(campaign.companions)) {
+ const companion = await dispatch("npcs/get_npc", { uid, id: companionId }, { root: true });
+
+ // Delete companion if:
+ // - the NPC doesn't exit
+ // - there are no players with companions in this campaign
+ // - there is no player with this NPC as companion in the campaign
+ if (!companion || !player_companions.length || !player_companions.includes(companionId)) {
+ await dispatch("delete_companion", { id, companionId });
+ console.warn(`Ghost companion ${companionId} deleted from campaign`);
+ } else {
+ // If the companion has no curHp, set it
+ if (campaign_companion.curHp === undefined) {
+ await dispatch("update_campaign_entity", {
+ uid,
+ campaignId: id,
+ type: "companions",
+ id: companionId,
+ property: "curHp",
+ value: companion.hit_points,
+ });
+ }
+ }
+ }
+ }
+
+ // Remove ghost items
+ if (campaign.inventory && campaign.inventory.items) {
+ for (const [item_id, item] of Object.entries(campaign.inventory.items)) {
+ if (item.linked_item && item.linked_item.custom) {
+ const linked_item = await dispatch(
+ "items/get_item",
+ { uid, id: item.linked_item.key },
+ { root: true }
+ );
+
+ // If the item doesn't exist, remove the item link
+ if (!linked_item) {
+ await services.updateCampaign(uid, id, `/inventory/items/${item_id}`, {
+ linked_item: null,
+ });
+ delete item.linked_item;
+ console.warn(`Ghost item link deleted from ${item.public_name}`);
+ }
+ }
+ }
+ }
+
+ return campaign;
+ },
+
+ /**
+ * Sets a campaign as active
+ * used to display at the top of the user content overview /content
+ *
+ * @param {string} id
+ */
+ async set_active_campaign({ commit, dispatch, rootGetters }, id) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.setActiveCampaign(uid, id);
+ commit("SET_ACTIVE_CAMPAIGN", id);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Adds a newly created campaign for a user
+ * A user can only add campaigns for themselves so we use the uid from the store
+ *
+ * @param {object} campaign
+ * @returns {string} the id of the newly added campaign
+ */
+ async add_campaign({ rootGetters, commit, dispatch }, campaign) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ const available_slots = rootGetters.tier.benefits.campaigns;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ const used_slots = await services.getCampaignCount(uid);
+
+ if (used_slots >= available_slots) {
+ throw "Not enough slots";
+ }
+ try {
+ const search_campaign = convert_campaign(campaign);
+ const id = await services.addCampaign(uid, campaign, search_campaign);
+ commit("SET_CAMPAIGN", { uid, id, search_campaign });
+
+ const new_count = await services.updateCampaignCount(uid, 1);
+ commit("SET_CAMPAIGN_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return id;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates and existing campaign
+ * Only updates properties that are present search_campaigns
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {object} campaign
+ */
+ async update_campaign({ commit, dispatch }, { uid, id, campaign }) {
+ if (uid) {
+ try {
+ const services = await dispatch("get_campaign_services");
+ const search_campaign = convert_campaign(campaign);
+ await services.updateCampaign(uid, id, "", search_campaign, true);
+
+ // Update all the props in the cached campaign
+ for (const [property, value] of Object.entries(campaign)) {
+ commit("SET_CACHED_PROP", { uid, id, property, value });
+ }
+
+ commit("SET_CAMPAIGN", { uid, id, search_campaign });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes a campaign
+ * - Deletes all encounters for this campaign
+ * - Removes campaign_id from players linked to the campaign
+ *
+ * @param {object} id
+ */
+ async delete_campaign({ rootGetters, commit, dispatch }, id) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ const campaign = await dispatch("get_campaign", { uid, id });
+ await services.deleteCampaign(uid, id);
+
+ // Delete all encounters of the campaign
+ dispatch("encounters/delete_campaign_encounters", id, { root: true });
+
+ // Remove campaign_id from players linked to the campaign
+ for (const playerId in campaign.players) {
+ dispatch(
+ "players/set_player_prop",
+ { uid, id: playerId, property: "campaign_id", value: null },
+ { root: true }
+ );
+ }
+
+ commit("REMOVE_CAMPAIGN", id);
+ commit("REMOVE_CACHED_CAMPAIGN", { uid, id });
+
+ // Update campaign count
+ const new_count = await services.updateCampaignCount(uid, -1);
+ commit("SET_CAMPAIGN_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Add a new player to the campaign
+ *
+ * @param {string} playerId
+ * @param {object} campaign search_campaign object (needed for campaignId and player_count)
+ */
+ async add_player({ rootGetters, commit, dispatch }, { playerId, campaign }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ const player = await dispatch("players/get_player", { uid, id: playerId }, { root: true });
+ const campaign_player = { curHp: player.maxHp };
+
+ // If the campaign has experience advancement
+ // make sure the player has experience points set
+ if (campaign.advancement === "experience" && player.experience === undefined) {
+ await dispatch(
+ "players/set_player_prop",
+ { uid, id: playerId, property: "experience", value: 0 },
+ { root: true }
+ );
+ }
+
+ // Set the campaign_id on the player
+ await dispatch(
+ "players/set_player_prop",
+ { uid, id: playerId, property: "campaign_id", value: campaign.key },
+ { root: true }
+ );
+
+ // Add companions
+ if (player.companions) {
+ for (const companionId in player.companions) {
+ let companion = await dispatch(
+ "npcs/get_npc",
+ { uid, id: companionId },
+ { root: true }
+ );
+
+ // If the NPC doesn't exist, delete the companion form the player
+ if (!companion) {
+ await dispatch(
+ "players/delete_companion",
+ { uid, playerId, id: companionId },
+ { root: true }
+ );
+ } else {
+ companion = { curHp: companion.hit_points }; // Only need the hit_points
+ // Add the companion to the campaign
+ await dispatch("update_companion", {
+ uid,
+ id: campaign.key,
+ companionId,
+ property: "curHp",
+ value: companion.curHp,
+ });
+ commit("SET_COMPANION", { uid, id: campaign.key, companionId, companion });
+ }
+ }
+ }
+
+ await services.setPlayer(uid, campaign.key, playerId, campaign_player);
+ commit("SET_PLAYER", { uid, id: campaign.key, playerId, player });
+
+ const new_count = await services.updatePlayerCount(uid, campaign.key, 1);
+ commit("SET_PLAYER_COUNT", { uid, id: campaign.key, new_count });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates the player_count of a campaign
+ *
+ * @param {string} id campaignId
+ * @param {number} new_count
+ */
+ async update_player_count({ rootGetters, commit, dispatch }, { id, new_count }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.updateSearchCampaign(uid, id, "", { player_count: new_count });
+ commit("SET_PLAYER_COUNT", { uid, id, new_count });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Overwrites an existing player with a new object
+ *
+ * @param {string} id campaignId
+ * @param {string} playerId
+ * @param {object} player
+ */
+ async edit_campaign_player({ rootGetters, commit, dispatch }, { id, playerId, player }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.setPlayer(uid, id, playerId, player);
+ commit("SET_PLAYER", { uid, id, playerId, player });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates a single property for a player or compantion
+ *
+ * @param {string} uid
+ * @param {string} campaignId
+ * @param {string} type Entity type players/companions
+ * @param {string} id entityId
+ * @param {string} property
+ * @param {string|number|boolean} value
+ */
+ async update_campaign_entity(
+ { commit, dispatch },
+ { uid, campaignId, type, id, property, value }
+ ) {
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ const path = `/${type}/${id}`;
+ try {
+ await services.updateCampaign(uid, campaignId, path, { [property]: value });
+ commit("UPDATE_CAMPAIGN_ENTITY", { uid, campaignId, type, id, property, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates a transformed player or companion
+ *
+ * @param {string} uid
+ * @param {string} campaignId
+ * @param {string} type Entity type players/companions
+ * @param {string} id entityId
+ * @param {string} property
+ * @param {number} value
+ */
+ async update_transformed_entity(
+ { commit, dispatch },
+ { uid, campaignId, type, id, property, value }
+ ) {
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ const path = `/${type}/${id}/transformed`;
+ try {
+ await services.updateCampaign(uid, campaignId, path, { [property]: value });
+ commit("UPDATE_TRANSFORMED_ENTITY", { uid, campaignId, type, id, property, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates a companion in a campaign
+ * Can also be used to add the companion to the campaign
+ *
+ * @param {string} id campaignId
+ * @param {string} companionId
+ * @param {string} property
+ * @param {string|number} value
+ */
+ async update_companion({ dispatch }, { uid, id, companionId, property, value }) {
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ const path = `/companions/${companionId}`;
+ await services.updateCampaign(uid, id, path, { [property]: value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes a companion from a campaign
+ *
+ * @param {string} id campaignId
+ * @param {string} companionId
+ */
+ async delete_companion({ rootGetters, commit, dispatch }, { id, companionId }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.deleteCompanion(uid, id, companionId);
+ commit("DELETE_COMPANION", { uid, id, companionId });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes a player from a campaign
+ * - Remove 1 from player_count
+ * - Remove the campaign_id from the player
+ * - Delete the companions of the player from the campaign
+ *
+ * When the delete_player function is called during get_campaign,
+ * it is possible the search_campaigns aren't fetched
+ * in this case the player_count can't be found in the store, but it's sent from the get_campaign function.
+ *
+ * @param {object} campaign
+ * @param {number} player_count
+ * @returns {string} the id of the newly added campaign
+ */
+ async delete_player({ state, rootGetters, commit, dispatch }, { id, player }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ const campaign = state.campaigns ? state.campaigns[id] : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ // Delete campaign_id
+ await dispatch(
+ "players/set_player_prop",
+ { uid, id: player.key, property: "campaign_id", value: null },
+ { root: true }
+ );
+
+ // Delete companions
+ if (player.companions) {
+ for (const companionId of Object.keys(player.companions)) {
+ await dispatch("delete_companion", { id, companionId });
+ }
+ }
+ await services.deletePlayer(uid, id, player.key);
+ commit("DELETE_PLAYER", { uid, id, playerId: player.key });
+
+ const new_count = await services.updatePlayerCount(uid, campaign.key, -1);
+ commit("SET_PLAYER_COUNT", { uid, id: campaign.key, new_count });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ async set_death_save({ dispatch, commit, rootGetters }, { campaignId, type, id, index, value }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ const path = `/${type}/${id}/saves`;
+ try {
+ await services.updateCampaign(uid, campaignId, path, { [index]: value });
+ commit("SET_DEATH_SAVE", { uid, campaignId, type, id, index, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ async stabilize_entity({ dispatch }, { uid, campaignId, type, id }) {
+ const update = {
+ dead: null,
+ saves: null,
+ stable: true,
+ };
+ for (const [property, value] of Object.entries(update)) {
+ dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
+ }
+ },
+
+ async kill_entity({ dispatch }, { uid, campaignId, type, id }) {
+ const update = {
+ saves: null,
+ stable: null,
+ dead: true,
+ };
+ for (const [property, value] of Object.entries(update)) {
+ dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
+ }
+ },
+
+ async revive_entity({ dispatch }, { uid, campaignId, type, id, curHp }) {
+ const update = {
+ dead: null,
+ saves: null,
+ stable: null,
+ curHp,
+ };
+ for (const [property, value] of Object.entries(update)) {
+ dispatch("update_campaign_entity", { uid, campaignId, type, id, property, value });
+ }
+ },
+
+ /**
+ * Updates a non-nested property of a campaign
+ *
+ * @param {string} id campaignId
+ * @param {string} property
+ * @param {any} value
+ */
+ async set_campaign_prop({ rootGetters, commit, dispatch }, { id, property, value }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.updateCampaign(uid, id, "", { [property]: value });
+ commit("SET_CACHED_PROP", { uid, id, property, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Update the "share" property with the latest value
+ * Shares can be seen by players on the public intiative list
+ *
+ * @param {string} id campaignId
+ * @param {object} share Share object
+ */
+ async set_share({ rootGetters, dispatch }, { id, share }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.setShare(uid, id, share);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Sets the currency in the inventory of a campaign
+ *
+ * @param {string} id campaignId
+ * @param {object} share Share object
+ */
+ async set_campaign_currency({ rootGetters, commit, dispatch }, { campaignId, value }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.updateCampaign(uid, campaignId, "/inventory", { currency: value });
+ commit("SET_CURRENCY", { uid, campaignId, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Adds an item to a campaign inventory
+ *
+ * @param {string} id campaignId
+ * @param {object} share Share object
+ */
+ async add_campaign_item({ rootGetters, commit, dispatch }, { campaignId, item }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ const id = await services.addItem(uid, campaignId, item);
+ commit("SET_ITEM", { uid, campaignId, id, item });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Identifies an item in a campaign, so players can see the linked item.
+ *
+ * @param {string} id campaignId
+ * @param {object} share Share object
+ */
+ async identify_campaign_item({ rootGetters, commit, dispatch }, { campaignId, id, value }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.updateCampaign(uid, campaignId, `/inventory/items/${id}`, {
+ identified: value,
+ });
+ commit("IDENTIFY_ITEM", { uid, campaignId, id, value });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes an item from a campaign inventory
+ *
+ * @param {string} id campaignId
+ * @param {object} share Share object
+ */
+ async delete_campaign_item({ rootGetters, commit, dispatch }, { campaignId, id }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_campaign_services");
+ try {
+ await services.updateCampaign(uid, campaignId, `/inventory/items`, { [id]: null });
+ commit("DELETE_ITEM", { uid, campaignId, id });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ clear_campaign_store({ commit, rootGetters }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ commit("CLEAR_STORE");
+ }
+ },
};
const campaign_mutations = {
- SET_CAMPAIGN_SERVICES(state, payload) { Vue.set(state, "campaign_services", payload); },
- SET_CAMPAIGNS(state, payload) { Vue.set(state, "campaigns", payload); },
- SET_CAMPAIGN_COUNT(state, value) { Vue.set(state, "campaign_count", value); },
- SET_CAMPAIGN(state, { id, search_campaign }) {
- if(state.campaigns) {
- Vue.set(state.campaigns, id, search_campaign);
- } else {
- Vue.set(state, "campaigns", { [id]: search_campaign });
- }
- },
- REMOVE_CAMPAIGN(state, id) {
- Vue.delete(state.campaigns, id);
- },
- SET_ACTIVE_CAMPAIGN(state, id) { Vue.set(state, "active_campaign", id); },
- SET_CACHED_CAMPAIGN(state, { uid, id, campaign }) {
- if(state.cached_campaigns[uid]) {
- Vue.set(state.cached_campaigns[uid], id, campaign);
- } else {
- Vue.set(state.cached_campaigns, uid, { [id]: campaign });
- }
- },
- SET_CACHED_PROP(state, { uid, id, property, value}) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
- Vue.set(state.cached_campaigns[uid][id], property, value);
- }
- },
- REMOVE_CACHED_CAMPAIGN(state, { uid, id }) {
- if(state.cached_campaigns[uid]) {
- Vue.delete(state.cached_campaigns[uid], id);
- }
- },
-
- // CAMPAIGN PLAYERS
- ADD_PLAYER(state, { uid, id, playerId, player }) {
- if(state.cached_campaigns[uid][id].players) {
- Vue.set(state.cached_campaigns[uid][id].players, playerId, player);
- } else {
- Vue.set(state.cached_campaigns[uid][id], "players", { [playerId]: player });
- }
- },
- SET_PLAYER_COUNT(state, { id, new_count }) {
- if(state.campaigns && state.campaigns[id]) {
- Vue.set(state.campaigns[id], "player_count", new_count);
- }
- },
- SET_PLAYER(state, { uid, id, playerId, player }) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
- if(state.cached_campaigns[uid][id].players) {
- Vue.set(state.cached_campaigns[uid][id].players, playerId, player);
- } else {
- Vue.set(state.cached_campaigns[uid][id], "players", { [playerId]: player });
- }
- }
- },
- DELETE_PLAYER(state, { uid, id, playerId }) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
- Vue.delete(state.cached_campaigns[uid][id].players, playerId);
- }
- },
- UPDATE_CAMPAIGN_ENTITY(state, { uid, campaignId, type, id, property, value }) {
- if(value === null) {
- Vue.delete(state.cached_campaigns[uid][campaignId][type][id], property);
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId][type][id], property, value);
- }
- },
- UPDATE_TRANSFORMED_ENTITY(state, { uid, campaignId, type, id, property, value }) {
- if(value === null) {
- Vue.delete(state.cached_campaigns[uid][campaignId][type][id].transformed, property);
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId][type][id].transformed, property, value);
- }
- },
- SET_DEATH_SAVE(state, { uid, campaignId, type, id, index, value }) {
- if(value === null) {
- Vue.delete(state.cached_campaigns[uid][campaignId][type][id].saves, index);
- } else {
- if(state.cached_campaigns[uid][campaignId][type][id].saves) {
- Vue.set(state.cached_campaigns[uid][campaignId][type][id].saves, index, value);
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId][type][id], "saves", { [index]: value });
- }
- }
- },
- SET_COMPANION(state, { uid, id, companionId, companion }) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
- if(state.cached_campaigns[uid][id].companions) {
- Vue.set(state.cached_campaigns[uid][id].companions, companionId, companion);
- } else {
- Vue.set(state.cached_campaigns[uid][id], "companions", { [companionId]: companion });
- }
- }
- },
- DELETE_COMPANION(state, { uid, id, companionId }) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
- Vue.delete(state.cached_campaigns[uid][id].companions, companionId);
- }
- },
- SET_CURRENCY(state, { uid, campaignId, value}) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][campaignId]) {
- if(state.cached_campaigns[uid][campaignId].inventory) {
- Vue.set(state.cached_campaigns[uid][campaignId].inventory, "currency", value);
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId], "inventory", { currency: value });
- }
- }
- },
- SET_ITEM(state, { uid, campaignId, id, item }) {
- if(state.cached_campaigns[uid] && state.cached_campaigns[uid][campaignId]) {
- if(state.cached_campaigns[uid][campaignId].inventory) {
- if(state.cached_campaigns[uid][campaignId].inventory.items) {
- Vue.set(state.cached_campaigns[uid][campaignId].inventory.items, id, item);
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId].inventory, "items", { [id]: item });
- }
- } else {
- Vue.set(state.cached_campaigns[uid][campaignId], "inventory", { items: { [id]: item } });
- }
- }
- },
- IDENTIFY_ITEM(state, { uid, campaignId, id, value }) {
- Vue.set(state.cached_campaigns[uid][campaignId].inventory.items[id], "identified", value);
- },
- DELETE_ITEM(state, { uid, campaignId, id }) {
- Vue.delete(state.cached_campaigns[uid][campaignId].inventory.items, id);
- },
- CLEAR_STORE(state) {
- Vue.set(state, "active_campaign", undefined);
- Vue.set(state, "campaigns", undefined);
- Vue.set(state, "campaign_count", 0);
- }
+ SET_CAMPAIGN_SERVICES(state, payload) {
+ Vue.set(state, "campaign_services", payload);
+ },
+ SET_CAMPAIGNS(state, payload) {
+ Vue.set(state, "campaigns", payload);
+ },
+ SET_CAMPAIGN_COUNT(state, value) {
+ Vue.set(state, "campaign_count", value);
+ },
+ SET_CAMPAIGN(state, { id, search_campaign }) {
+ if (state.campaigns) {
+ Vue.set(state.campaigns, id, search_campaign);
+ } else {
+ Vue.set(state, "campaigns", { [id]: search_campaign });
+ }
+ },
+ REMOVE_CAMPAIGN(state, id) {
+ Vue.delete(state.campaigns, id);
+ },
+ SET_ACTIVE_CAMPAIGN(state, id) {
+ Vue.set(state, "active_campaign", id);
+ },
+ SET_CACHED_CAMPAIGN(state, { uid, id, campaign }) {
+ if (state.cached_campaigns[uid]) {
+ Vue.set(state.cached_campaigns[uid], id, campaign);
+ } else {
+ Vue.set(state.cached_campaigns, uid, { [id]: campaign });
+ }
+ },
+ SET_CACHED_PROP(state, { uid, id, property, value }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
+ Vue.set(state.cached_campaigns[uid][id], property, value);
+ }
+ },
+ REMOVE_CACHED_CAMPAIGN(state, { uid, id }) {
+ if (state.cached_campaigns[uid]) {
+ Vue.delete(state.cached_campaigns[uid], id);
+ }
+ },
+
+ // CAMPAIGN PLAYERS
+ ADD_PLAYER(state, { uid, id, playerId, player }) {
+ if (state.cached_campaigns[uid][id].players) {
+ Vue.set(state.cached_campaigns[uid][id].players, playerId, player);
+ } else {
+ Vue.set(state.cached_campaigns[uid][id], "players", { [playerId]: player });
+ }
+ },
+ SET_PLAYER_COUNT(state, { id, new_count }) {
+ if (state.campaigns && state.campaigns[id]) {
+ Vue.set(state.campaigns[id], "player_count", new_count);
+ }
+ },
+ SET_PLAYER(state, { uid, id, playerId, player }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
+ if (state.cached_campaigns[uid][id].players) {
+ Vue.set(state.cached_campaigns[uid][id].players, playerId, player);
+ } else {
+ Vue.set(state.cached_campaigns[uid][id], "players", { [playerId]: player });
+ }
+ }
+ },
+ DELETE_PLAYER(state, { uid, id, playerId }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
+ Vue.delete(state.cached_campaigns[uid][id].players, playerId);
+ }
+ },
+ UPDATE_CAMPAIGN_ENTITY(state, { uid, campaignId, type, id, property, value }) {
+ if (value === null) {
+ Vue.delete(state.cached_campaigns[uid][campaignId][type][id], property);
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId][type][id], property, value);
+ }
+ },
+ UPDATE_TRANSFORMED_ENTITY(state, { uid, campaignId, type, id, property, value }) {
+ if (value === null) {
+ Vue.delete(state.cached_campaigns[uid][campaignId][type][id].transformed, property);
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId][type][id].transformed, property, value);
+ }
+ },
+ SET_DEATH_SAVE(state, { uid, campaignId, type, id, index, value }) {
+ if (value === null) {
+ Vue.delete(state.cached_campaigns[uid][campaignId][type][id].saves, index);
+ } else {
+ if (state.cached_campaigns[uid][campaignId][type][id].saves) {
+ Vue.set(state.cached_campaigns[uid][campaignId][type][id].saves, index, value);
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId][type][id], "saves", { [index]: value });
+ }
+ }
+ },
+ SET_COMPANION(state, { uid, id, companionId, companion }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
+ if (state.cached_campaigns[uid][id].companions) {
+ Vue.set(state.cached_campaigns[uid][id].companions, companionId, companion);
+ } else {
+ Vue.set(state.cached_campaigns[uid][id], "companions", { [companionId]: companion });
+ }
+ }
+ },
+ DELETE_COMPANION(state, { uid, id, companionId }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][id]) {
+ Vue.delete(state.cached_campaigns[uid][id].companions, companionId);
+ }
+ },
+ SET_CURRENCY(state, { uid, campaignId, value }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][campaignId]) {
+ if (state.cached_campaigns[uid][campaignId].inventory) {
+ Vue.set(state.cached_campaigns[uid][campaignId].inventory, "currency", value);
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId], "inventory", { currency: value });
+ }
+ }
+ },
+ SET_ITEM(state, { uid, campaignId, id, item }) {
+ if (state.cached_campaigns[uid] && state.cached_campaigns[uid][campaignId]) {
+ if (state.cached_campaigns[uid][campaignId].inventory) {
+ if (state.cached_campaigns[uid][campaignId].inventory.items) {
+ Vue.set(state.cached_campaigns[uid][campaignId].inventory.items, id, item);
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId].inventory, "items", { [id]: item });
+ }
+ } else {
+ Vue.set(state.cached_campaigns[uid][campaignId], "inventory", { items: { [id]: item } });
+ }
+ }
+ },
+ IDENTIFY_ITEM(state, { uid, campaignId, id, value }) {
+ Vue.set(state.cached_campaigns[uid][campaignId].inventory.items[id], "identified", value);
+ },
+ DELETE_ITEM(state, { uid, campaignId, id }) {
+ Vue.delete(state.cached_campaigns[uid][campaignId].inventory.items, id);
+ },
+ CLEAR_STORE(state) {
+ Vue.set(state, "active_campaign", undefined);
+ Vue.set(state, "campaigns", undefined);
+ Vue.set(state, "campaign_count", 0);
+ },
};
export default {
- namespaced: true,
- state: campaign_state,
- getters: campaign_getters,
- actions: campaign_actions,
- mutations: campaign_mutations
-}
+ namespaced: true,
+ state: campaign_state,
+ getters: campaign_getters,
+ actions: campaign_actions,
+ mutations: campaign_mutations,
+};
diff --git a/src/store/modules/userContent/npcs.js b/src/store/modules/userContent/npcs.js
index f76f37511..d87ee9b25 100644
--- a/src/store/modules/userContent/npcs.js
+++ b/src/store/modules/userContent/npcs.js
@@ -1,310 +1,328 @@
-import Vue from 'vue';
-import { npcServices } from "src/services/npcs";
+import Vue from "vue";
+import { npcServices } from "src/services/npcs";
import _ from "lodash";
// Converts a full npc to a search_npc
const convert_npc = (npc) => {
- const properties = [
- "name",
- "challenge_rating",
- "avatar",
- "storage_avatar",
- "type"
- ];
- const returnNpc = {};
-
- for(const prop of properties) {
- if(npc.hasOwnProperty(prop)) {
- returnNpc[prop] = (prop === "name") ? npc[prop].toLowerCase() : npc[prop];
- }
+ const properties = ["name", "challenge_rating", "avatar", "storage_avatar", "type"];
+ const returnNpc = {};
+
+ for (const prop of properties) {
+ if (npc.hasOwnProperty(prop)) {
+ returnNpc[prop] = prop === "name" ? npc[prop].toLowerCase() : npc[prop];
+ }
}
return returnNpc;
-}
+};
const npc_state = () => ({
- npc_services: null,
- cached_npcs: {},
- npc_count: 0,
- npcs: undefined
+ npc_services: null,
+ cached_npcs: {},
+ npc_count: 0,
+ npcs: undefined,
});
const npc_getters = {
- npcs: (state) => {
- // Convert object to sorted array
- return _.chain(state.npcs)
- .filter((npc, key) => {
- npc.key = key;
- return npc;
- }).orderBy("name", "asc").value();
- },
- npc_count: (state) => { return state.npc_count; },
- npc_services: (state) => { return state.npc_services; }
+ npcs: (state) => {
+ // Convert object to sorted array
+ return _.chain(state.npcs)
+ .filter((npc, key) => {
+ npc.key = key;
+ return npc;
+ })
+ .orderBy("name", "asc")
+ .value();
+ },
+ npc_count: (state) => {
+ return state.npc_count;
+ },
+ npc_services: (state) => {
+ return state.npc_services;
+ },
};
const npc_actions = {
- async get_npc_services({ getters, commit }) {
- if(getters.npc_services === null || !Object.keys(getters.npc_services).length) {
- commit("SET_NPC_SERVICES", new npcServices);
- }
- return getters.npc_services;
- },
+ async get_npc_services({ getters, commit }) {
+ if (getters.npc_services === null || !Object.keys(getters.npc_services).length) {
+ commit("SET_NPC_SERVICES", new npcServices());
+ }
+ return getters.npc_services;
+ },
+
+ /**
+ * Fetches all the search_npcs for a user
+ * and stores them in npcs
+ */
+ async get_npcs({ state, rootGetters, dispatch, commit }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ let npcs = state.npcs ? state.npcs : undefined;
+
+ if (!npcs && uid) {
+ const services = await dispatch("get_npc_services");
+ try {
+ npcs = await services.getNpcs(uid);
+ commit("SET_NPCS", npcs || {});
+ } catch (error) {
+ throw error;
+ }
+ }
+ return npcs;
+ },
- /**
- * Fetches all the search_npcs for a user
- * and stores them in npcs
- */
- async get_npcs({ state, rootGetters, dispatch, commit }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- let npcs = (state.npcs) ? state.npcs : undefined;
+ /**
+ * Fetches the total count of npcs for a user
+ * and stores it in npc_count
+ */
+ async fetch_npc_count({ rootGetters, commit, dispatch }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+ try {
+ const count = (await services.getNpcCount(uid)) || 0;
+ commit("SET_NPC_COUNT", count);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
- if(!npcs && uid) {
- const services = await dispatch("get_npc_services");
- try {
- npcs = await services.getNpcs(uid);
- commit("SET_NPCS", npcs || {});
- } catch(error) {
- throw error;
- }
- }
- return npcs;
- },
-
- /**
- * Fetches the total count of npcs for a user
- * and stores it in npc_count
- */
- async fetch_npc_count({ rootGetters, commit, dispatch }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_npc_services");
- try {
- const count = await services.getNpcCount(uid) || 0;
- commit("SET_NPC_COUNT", count);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- async get_npc({ state, commit, dispatch }, { uid, id }) {
- let npc = (state.cached_npcs[uid]) ? state.cached_npcs[uid][id] : undefined;
+ async get_npc({ state, commit, dispatch }, { uid, id }) {
+ let npc = state.cached_npcs[uid] ? state.cached_npcs[uid][id] : undefined;
- // The npc is not in the store and needs to be fetched from the database
- // If the NPC is not found in firebase, it returns null
- // We don't have to check for null NPCs again, we know they don't exist
- // Therefore we only do a call to firebase if npc === undefined
- if(npc === undefined) {
- const services = await dispatch("get_npc_services");
- try {
- npc = await services.getNpc(uid, id);
- commit("SET_CACHED_NPC", { uid, id, npc });
- } catch(error) {
- throw error;
- }
- }
- return npc;
- },
+ // The npc is not in the store and needs to be fetched from the database
+ // If the NPC is not found in firebase, it returns null
+ // We don't have to check for null NPCs again, we know they don't exist
+ // Therefore we only do a call to firebase if npc === undefined
+ if (npc === undefined) {
+ const services = await dispatch("get_npc_services");
+ try {
+ npc = await services.getNpc(uid, id);
+ commit("SET_CACHED_NPC", { uid, id, npc });
+ } catch (error) {
+ throw error;
+ }
+ }
+ return npc;
+ },
- /**
- * Adds a newly created NPC for a user
- * A user can only add NPC's for themselves so we use the uid from the store
- *
- * @param {object} npc
- * @returns {string} the id of the newly added npc
- */
- async add_npc({ rootGetters, commit, dispatch }, npc) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- const available_slots = rootGetters.tier.benefits.npcs;
+ /**
+ * Adds a newly created NPC for a user
+ * A user can only add NPC's for themselves so we use the uid from the store
+ *
+ * @param {object} npc
+ * @returns {string} the id of the newly added npc
+ */
+ async add_npc({ rootGetters, commit, dispatch }, npc) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ const available_slots = rootGetters.tier.benefits.npcs;
- if(uid) {
- const services = await dispatch("get_npc_services");
- const used_slots = await services.getNpcCount(uid);
-
- if(used_slots >= available_slots) {
- throw "Not enough slots";
- }
- try {
- const search_npc = convert_npc(npc);
- const id = await services.addNpc(uid, npc, search_npc);
- commit("SET_NPC", { id, search_npc });
- commit("SET_CACHED_NPC", { uid, id, npc });
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+ const used_slots = await services.getNpcCount(uid);
- const new_count = await services.updateNpcCount(uid, 1);
- commit("SET_NPC_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return id;
- } catch(error) {
- throw error;
- }
- }
- },
+ if (used_slots >= available_slots) {
+ return "Not enough slots";
+ }
+ try {
+ const search_npc = convert_npc(npc);
+ const id = await services.addNpc(uid, npc, search_npc);
+ commit("SET_NPC", { id, search_npc });
+ commit("SET_CACHED_NPC", { uid, id, npc });
- /**
- * Updates and existing NPC
- * It is possible to edit the NPC of another user (for companions)
- * therefore we send the uid from where the function is called
- *
- * @param {string} uid
- * @param {string} id
- * @param {object} npc
- */
- async edit_npc({ commit, dispatch }, { uid, id, npc }) {
- if(uid) {
- const services = await dispatch("get_npc_services");
- try {
- const search_npc = convert_npc(npc);
- await services.editNpc(uid, id, npc, search_npc);
- commit("SET_NPC", { id, search_npc });
- commit("SET_CACHED_NPC", { uid, id, npc });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
+ const new_count = await services.updateNpcCount(uid, 1);
+ commit("SET_NPC_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return id;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
- /**
- * Updates a single (non nested) property of an NPC
- *
- * @param {string} uid
- * @param {string} id
- * @param {string} property
- * @param {string|number} value
- */
- async update_npc_prop({ commit, dispatch }, { uid, id, property, value }) {
- if(uid) {
- const services = await dispatch("get_npc_services");
- const update_search = [
- "name",
- "challenge_rating",
- "avatar",
- "storage_avatar",
- "type"
- ].includes(property);
- try {
- await services.updateNpc(uid, id, "", { [property]: value}, update_search);
- commit("SET_NPC_PROP", { uid, id, property, value, update_search });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
+ /**
+ * Updates and existing NPC
+ * It is possible to edit the NPC of another user (for companions)
+ * therefore we send the uid from where the function is called
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {object} npc
+ */
+ async edit_npc({ commit, dispatch }, { uid, id, npc }) {
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+ try {
+ const search_npc = convert_npc(npc);
+ await services.editNpc(uid, id, npc, search_npc);
+ commit("SET_NPC", { id, search_npc });
+ commit("SET_CACHED_NPC", { uid, id, npc });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
- /**
- * Deletes an existing NPC
- * A user can only delete their own NPC's so use uid from the store
- *
- * @param {string} id
- */
- async delete_npc({ rootGetters, commit, dispatch }, id) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_npc_services");
- try {
- const npc = await dispatch("get_npc", { uid, id });
-
- // DELETE COMPANION FROM PLAYER
- if(npc.player_id) {
- const player = await dispatch("players/get_player", { uid, id: npc.player_id }, { root: true });
-
- // Remove the companion from the player
- await dispatch("players/delete_companion", { uid, playerId: npc.player_id, id }, { root: true });
+ /**
+ * Updates a single (non nested) property of an NPC
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {string} property
+ * @param {string|number} value
+ */
+ async update_npc_prop({ commit, dispatch }, { uid, id, property, value }) {
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+ const update_search = [
+ "name",
+ "challenge_rating",
+ "avatar",
+ "storage_avatar",
+ "type",
+ ].includes(property);
+ try {
+ await services.updateNpc(uid, id, "", { [property]: value }, update_search);
+ commit("SET_NPC_PROP", { uid, id, property, value, update_search });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
- // Remove the companion from the campaign
- if(player.campaign_id) {
- await dispatch("campaigns/delete_companion", { id: player.campaign_id, companionId: id }, { root: true });
- }
- }
+ /**
+ * Deletes an existing NPC
+ * A user can only delete their own NPC's so use uid from the store
+ *
+ * @param {string} id
+ */
+ async delete_npc({ rootGetters, commit, dispatch }, id) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+ try {
+ const npc = await dispatch("get_npc", { uid, id });
- // Delete the NPC
- await services.deleteNpc(uid, id);
- commit("REMOVE_NPC", id);
- commit("REMOVE_CACHED_NPC", { uid, id });
+ // DELETE COMPANION FROM PLAYER
+ if (npc.player_id) {
+ const player = await dispatch(
+ "players/get_player",
+ { uid, id: npc.player_id },
+ { root: true }
+ );
- const new_count = await services.updateNpcCount(uid, -1);
- commit("SET_NPC_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
+ // Remove the companion from the player
+ await dispatch(
+ "players/delete_companion",
+ { uid, playerId: npc.player_id, id },
+ { root: true }
+ );
- /**
- * Gets all REAL NPCs from a USER
- */
- async get_full_npcs({ rootGetters, commit, dispatch }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if (uid) {
- const services = await dispatch("get_npc_services");
+ // Remove the companion from the campaign
+ if (player.campaign_id) {
+ await dispatch(
+ "campaigns/delete_companion",
+ { id: player.campaign_id, companionId: id },
+ { root: true }
+ );
+ }
+ }
- try {
- const all_npcs = await services.getFullNpcs(uid);
- commit("SET_CACHED_NPCS", { uid, npcs: all_npcs })
- return all_npcs;
- } catch(error) {
- throw error;
- }
- }
- },
+ // Delete the NPC
+ await services.deleteNpc(uid, id);
+ commit("REMOVE_NPC", id);
+ commit("REMOVE_CACHED_NPC", { uid, id });
- clear_npc_store({ commit, rootGetters }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- commit("CLEAR_STORE");
- }
- }
+ const new_count = await services.updateNpcCount(uid, -1);
+ commit("SET_NPC_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Gets all REAL NPCs from a USER
+ */
+ async get_full_npcs({ rootGetters, commit, dispatch }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_npc_services");
+
+ try {
+ const all_npcs = await services.getFullNpcs(uid);
+ commit("SET_CACHED_NPCS", { uid, npcs: all_npcs });
+ return all_npcs;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ clear_npc_store({ commit, rootGetters }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ commit("CLEAR_STORE");
+ }
+ },
};
const npc_mutations = {
- SET_NPC_SERVICES(state, payload) { Vue.set(state, "npc_services", payload); },
- SET_NPC_COUNT(state, value) { Vue.set(state, "npc_count", value); },
- SET_NPCS(state, value) { Vue.set(state, "npcs", value); },
- SET_CACHED_NPCS(state, { uid, npcs }) {
- Vue.set(state.cached_npcs, uid, npcs);
- },
- SET_CACHED_NPC(state, { uid, id, npc }) {
- if(state.cached_npcs[uid]) {
- Vue.set(state.cached_npcs[uid], id, npc);
- } else {
- Vue.set(state.cached_npcs, uid, { [id]: npc });
- }
- },
- SET_NPC_PROP(state, { uid, id, property, value, update_search }) {
- if(state.cached_npcs[uid] && state.cached_npcs[uid][id]) {
- Vue.set(state.cached_npcs[uid][id], property, value);
- }
- if(update_search && state.npcs && state.npcs[id]) {
- Vue.set(state.npcs[id], property, value);
- }
- },
- SET_NPC(state, { id, search_npc }) {
- if(state.npcs) {
- Vue.set(state.npcs, id, search_npc);
- } else {
- Vue.set(state, "npcs", { [id]: search_npc });
- }
- },
- REMOVE_NPC(state, id) {
- Vue.delete(state.npcs, id);
- },
- REMOVE_CACHED_NPC(state, { uid, id }) {
- if(state.cached_npcs[uid]) {
- Vue.delete(state.cached_npcs[uid], id);
- }
- },
- CLEAR_STORE(state) {
- Vue.set(state, "npcs", undefined);
- Vue.set(state, "npc_count", 0);
- }
+ SET_NPC_SERVICES(state, payload) {
+ Vue.set(state, "npc_services", payload);
+ },
+ SET_NPC_COUNT(state, value) {
+ Vue.set(state, "npc_count", value);
+ },
+ SET_NPCS(state, value) {
+ Vue.set(state, "npcs", value);
+ },
+ SET_CACHED_NPCS(state, { uid, npcs }) {
+ Vue.set(state.cached_npcs, uid, npcs);
+ },
+ SET_CACHED_NPC(state, { uid, id, npc }) {
+ if (state.cached_npcs[uid]) {
+ Vue.set(state.cached_npcs[uid], id, npc);
+ } else {
+ Vue.set(state.cached_npcs, uid, { [id]: npc });
+ }
+ },
+ SET_NPC_PROP(state, { uid, id, property, value, update_search }) {
+ if (state.cached_npcs[uid] && state.cached_npcs[uid][id]) {
+ Vue.set(state.cached_npcs[uid][id], property, value);
+ }
+ if (update_search && state.npcs && state.npcs[id]) {
+ Vue.set(state.npcs[id], property, value);
+ }
+ },
+ SET_NPC(state, { id, search_npc }) {
+ if (state.npcs) {
+ Vue.set(state.npcs, id, search_npc);
+ } else {
+ Vue.set(state, "npcs", { [id]: search_npc });
+ }
+ },
+ REMOVE_NPC(state, id) {
+ Vue.delete(state.npcs, id);
+ },
+ REMOVE_CACHED_NPC(state, { uid, id }) {
+ if (state.cached_npcs[uid]) {
+ Vue.delete(state.cached_npcs[uid], id);
+ }
+ },
+ CLEAR_STORE(state) {
+ Vue.set(state, "npcs", undefined);
+ Vue.set(state, "npc_count", 0);
+ },
};
export default {
- namespaced: true,
- state: npc_state,
- getters: npc_getters,
- actions: npc_actions,
- mutations: npc_mutations
-}
+ namespaced: true,
+ state: npc_state,
+ getters: npc_getters,
+ actions: npc_actions,
+ mutations: npc_mutations,
+};
diff --git a/src/store/modules/userContent/players.js b/src/store/modules/userContent/players.js
index 626938eca..8200c4d70 100644
--- a/src/store/modules/userContent/players.js
+++ b/src/store/modules/userContent/players.js
@@ -1,566 +1,652 @@
-import Vue from 'vue';
-import { playerServices } from "src/services/players";
-import { getCharacterSyncCharacter, characterToPlayer } from 'src/utils/generalFunctions';
-import _ from 'lodash';
+import Vue from "vue";
+import { playerServices } from "src/services/players";
+import { getCharacterSyncCharacter, characterToPlayer } from "src/utils/generalFunctions";
+import { skills } from "src/utils/generalConstants";
+import _ from "lodash";
// Parse number values to ints
const numberValues = [
- "ac",
- "experience",
- "initiative",
- "level",
- "maxHp",
- "passive_insight",
- "passive_investigation",
- "passive_perception",
- "speed",
- "strength",
- "dexterity",
- "constitution",
- "intelligence",
- "wisdom",
- "charisma"
+ "ac",
+ "experience",
+ "initiative",
+ "level",
+ "maxHp",
+ "passive_insight",
+ "passive_investigation",
+ "passive_perception",
+ "spell_save_dc",
+ "speed",
+ "strength",
+ "dexterity",
+ "constitution",
+ "intelligence",
+ "wisdom",
+ "charisma",
];
-const parseInts = (player) => {
- for(const [key, value] of Object.entries(player)) {
- if(numberValues.includes(key) && value !== undefined && value !== null) {
- player[key] = parseInt(value);
- }
- }
- return player;
+function filterSkills(playerSkills) {
+ const returnArray = [];
+ for (const skill of playerSkills) {
+ if (Object.keys(skills).includes(skill)) {
+ returnArray.push(skill);
+ }
+ }
+ return returnArray;
+}
+function filterUnusedAttributes(player) {
+ const unused_attributes = ["ac_bonus", "curHp", "tempHp", "maxHpMod"];
+
+ Object.keys(player).forEach((attribute) => {
+ if (unused_attributes.includes(attribute)) {
+ player[attribute] = null;
+ }
+ });
+ return player;
+}
+function parseInts(player) {
+ Object.entries(player).forEach(([key, value]) => {
+ if (numberValues.includes(key) && value !== undefined && value !== null && value !== "") {
+ player[key] = parseInt(value);
+ }
+ if (numberValues.includes(key) && value === "") {
+ player[key] = null;
+ }
+ });
+ return player;
}
// Converts a full player to a search_player
const convert_player = (player) => {
const properties = [
- "character_name",
- "avatar",
- "storage_avatar",
- "campaign_id",
- "sync_character",
- "companions"
+ "character_name",
+ "avatar",
+ "storage_avatar",
+ "campaign_id",
+ "sync_character",
+ "companions",
];
- const returnPlayer = {};
-
- for(const prop of properties) {
- if(player.hasOwnProperty(prop)) {
- returnPlayer[prop] = player[prop];
- }
+ const returnPlayer = {};
+
+ for (const prop of properties) {
+ if (player.hasOwnProperty(prop)) {
+ returnPlayer[prop] = player[prop];
+ }
}
return returnPlayer;
-}
+};
const player_state = () => ({
- player_services: null,
- players: undefined,
- player_count: 0,
- cached_players: {},
- characters: undefined
+ player_services: null,
+ players: undefined,
+ player_count: 0,
+ cached_players: {},
+ characters: undefined,
});
const player_getters = {
- players: (state) => {
- // Convert object to sorted array
- return _.chain(state.players)
- .filter((player, key) => {
- player.key = key;
- return player;
- }).orderBy("character_name", "asc").value();
- },
- characters: (state) => {
- // Convert object to sorted array
- return _.chain(state.characters)
- .filter((character, key) => {
- character.key = key;
- return character;
- }).orderBy("character_name", "asc").value();
- },
- player_count: (state) => { return state.player_count; },
- player_services: (state) => { return state.player_services; }
+ players: (state) => {
+ // Convert object to sorted array
+ return _.chain(state.players)
+ .filter((player, key) => {
+ player.key = key;
+ return player;
+ })
+ .orderBy("character_name", "asc")
+ .value();
+ },
+ characters: (state) => {
+ // Convert object to sorted array
+ return _.chain(state.characters)
+ .filter((character, key) => {
+ character.key = key;
+ return character;
+ })
+ .orderBy("character_name", "asc")
+ .value();
+ },
+ player_count: (state) => {
+ return state.player_count;
+ },
+ player_services: (state) => {
+ return state.player_services;
+ },
};
const player_actions = {
- async get_player_services({ getters, commit }) {
- if(getters.player_services === null || !Object.keys(getters.player_services).length) {
- commit("SET_PLAYER_SERVICES", new playerServices);
- }
- return getters.player_services;
- },
-
- /**
- * Fetches all the search_players for a user
- * and stores them in players
- */
- async get_players({ state, rootGetters, dispatch, commit }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- let players = (state.players) ? state.players : undefined;
-
- if(!players && uid) {
- const services = await dispatch("get_player_services");
- try {
- players = await services.getPlayers(uid);
- commit("SET_PLAYERS", players || {});
- } catch(error) {
- throw error;
- }
- }
- return players;
- },
-
- /**
- * Fetches a full player object
- *
- * - Delete ghost companions
- */
- async get_player({ state, commit, dispatch }, { uid, id }) {
- let player = (state.cached_players[uid]) ? state.cached_players[uid][id] : undefined;
- const services = await dispatch("get_player_services");
-
- // The player is not in the store and needs to be fetched from the database
- if(!player) {
- try {
- player = await services.getPlayer(uid, id);
- commit("SET_CACHED_PLAYER", { uid, id, player });
- } catch(error) {
- throw error;
- }
- }
-
- // Delete ghost companions
- if(player && player.companions) {
- for(const companionId of Object.keys(player.companions)) {
- const companion = await dispatch("npcs/get_npc", { uid, id: companionId }, { root: true });
-
- // If the NPC doesn't exist, delete the companion
- if(!companion) {
- await dispatch("delete_companion", {
- uid,
- playerId: id,
- id: companionId
- });
- delete player.companions[companionId];
- console.warn(`Ghost companion ${companionId} deleted from player`);
- }
- }
- }
-
- return player;
- },
-
- /**
- * Fetches the total count of players for a user
- * and stores it in player_count
- */
- async fetch_player_count({ rootGetters, commit, dispatch }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- const count = await services.getPlayerCount(uid) || 0;
- commit("SET_PLAYER_COUNT", count);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Fetches all the characters for a user
- * and stores them in characters
- */
- async get_characters({ state, rootGetters, dispatch, commit }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- let characters = (state.characters) ? state.characters : undefined;
-
- if(!characters && uid) {
- const services = await dispatch("get_player_services");
- try {
- const entries = await services.getCharacters(uid);
-
- if(entries) {
- characters = {};
- for(const [playerId, value] of Object.entries(entries)) {
- characters[playerId] = await services.getSearchPlayer(value.user, playerId);
- characters[playerId].user_id = value.user;
- }
- }
- commit("SET_CHARACTERS", characters || {});
- } catch(error) {
- throw error;
- }
- }
- return characters;
- },
-
- async get_owner_id({dispatch}, { uid, playerId }) {
- const services = await dispatch("get_player_services");
- try {
- const userId = await services.getOwner(uid, playerId);
- return userId.user;
- } catch(error) {
- throw error;
- }
- },
-
- /**
- * Adds a newly created player for a user
- * A user can only add players for themselves so we use the uid from the store
- *
- * @param {object} player
- * @returns {string} the id of the newly added player
- */
- async add_player({ rootGetters, commit, dispatch }, player) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- const available_slots = rootGetters.tier.benefits.players;
-
- if(uid) {
- const services = await dispatch("get_player_services");
- const used_slots = await services.getPlayerCount(uid);
-
- if(used_slots >= available_slots) {
- throw "Not enough slots";
- }
- try {
- player = parseInts(player);
- const search_player = convert_player(player);
- const id = await services.addPlayer(uid, player, search_player);
-
- // If there are companions, save the playerId in the NPC (So the player can edit this specific NPC)
- if(player.companions) {
- for(const npcId of Object.keys(player.companions)) {
- await dispatch("npcs/update_npc_prop", {
- uid,
- id: npcId,
- property: "player_id",
- value: id
- }, { root: true });
+ async get_player_services({ getters, commit }) {
+ if (getters.player_services === null || !Object.keys(getters.player_services).length) {
+ commit("SET_PLAYER_SERVICES", new playerServices());
+ }
+ return getters.player_services;
+ },
+
+ /**
+ * Fetches all the search_players for a user
+ * and stores them in players
+ */
+ async get_players({ state, rootGetters, dispatch, commit }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ let players = state.players ? state.players : undefined;
+
+ if (!players && uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ players = await services.getPlayers(uid);
+ commit("SET_PLAYERS", players || {});
+ } catch (error) {
+ throw error;
+ }
+ }
+ return players;
+ },
+
+ /**
+ * Fetches a full player object
+ *
+ * - Delete ghost companions
+ */
+ async get_player({ state, commit, dispatch }, { uid, id }) {
+ let player = state.cached_players[uid] ? state.cached_players[uid][id] : undefined;
+ const services = await dispatch("get_player_services");
+
+ // The player is not in the store and needs to be fetched from the database
+ if (!player) {
+ try {
+ player = await services.getPlayer(uid, id);
+ commit("SET_CACHED_PLAYER", { uid, id, player });
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ // Delete ghost companions
+ if (player && player.companions) {
+ for (const companionId of Object.keys(player.companions)) {
+ const companion = await dispatch("npcs/get_npc", { uid, id: companionId }, { root: true });
+
+ // If the NPC doesn't exist, delete the companion
+ if (!companion) {
+ await dispatch("delete_companion", {
+ uid,
+ playerId: id,
+ id: companionId,
+ });
+ delete player.companions[companionId];
+ console.warn(`Ghost companion ${companionId} deleted from player`);
+ }
+ }
+ }
+
+ return player;
+ },
+
+ /**
+ * Fetches the total count of players for a user
+ * and stores it in player_count
+ */
+ async fetch_player_count({ rootGetters, commit, dispatch }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ const count = (await services.getPlayerCount(uid)) || 0;
+ commit("SET_PLAYER_COUNT", count);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Fetches all the characters for a user
+ * and stores them in characters
+ */
+ async get_characters({ state, rootGetters, dispatch, commit }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ let characters = state.characters ? state.characters : undefined;
+
+ if (!characters && uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ const entries = await services.getCharacters(uid);
+
+ if (entries) {
+ characters = {};
+ for (const [playerId, value] of Object.entries(entries)) {
+ characters[playerId] = await services.getSearchPlayer(value.user, playerId);
+ characters[playerId].user_id = value.user;
}
- }
- commit("SET_PLAYER", { id, search_player });
- commit("SET_CACHED_PLAYER", { uid, id, player });
-
- const new_count = await services.updatePlayerCount(uid, 1);
- commit("SET_PLAYER_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return id;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Overwrites and existing player
- * It is possible to edit the player of another user (for companions)
- * therefore we send the uid from where the function is called
- *
- * @param {string} uid
- * @param {string} id
- * @param {object} player
- */
- async edit_player({ commit, dispatch }, { uid, id, player, companions=[], deleted_companions=[] }) {
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- player = parseInts(player);
- const search_player = convert_player(player);
- await services.editPlayer(uid, id, player, search_player);
-
- // If there are companions, save the playerId in the NPC (So the player can edit this specific NPC)
- for(const companion of companions) {
- await dispatch("npcs/update_npc_prop", {
- uid,
- id: companion.key,
- property: "player_id",
- value: id
- }, { root: true });
-
- // If the player is in a campaign, update the companion's curHp in the campaign
- if(player.campaign_id) {
- await dispatch("campaigns/update_companion", {
- uid,
- id: player.campaign_id,
- companionId: companion.key,
- property: "curHp",
- value: companion.hit_points
- }, { root: true });
- }
- }
-
- // Remove the player_id from an NPC if that NPC was removed as a companion
- for(const companionId of deleted_companions) {
- await dispatch("npcs/update_npc_prop", {
- uid,
- id: companionId,
- property: "player_id",
- value: null
- }, { root: true });
-
- // If the player is in a campaign, delete the companion from the campaign
- if(player.campaign_id) {
- await dispatch("campaigns/delete_companion", {
- id: player.campaign_id,
- companionId,
- }, { root: true });
- }
- }
-
- commit("UPDATE_SEARCH_PLAYER", { id, search_player });
- commit("SET_CACHED_PLAYER", { uid, id, player });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Sets the property of a player
- *
- * @param {string} uid
- * @param {string} id
- * @param {number} value
- */
- async set_player_prop({ commit, dispatch }, { uid, id, property, value }) {
- if(uid) {
- const services = await dispatch("get_player_services");
- const update_search = ["character_name", "avatar", "campaign_id", "sync_character"].includes(property);
- try {
- value = (numberValues.includes(value) && value !== null) ? parseInt(value) : value;
- await services.updatePlayer(uid, id, "", { [property]: value }, update_search);
- commit("SET_PLAYER_PROP", { uid, id, property, value, update_search });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
-
- /**
- * Removes the companion link from a player
- *
- * @param {string} uid
- * @param {string} playerId
- * @param {string} id companionId
- */
- async delete_companion({ commit, dispatch }, { uid, playerId, id }) {
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- await services.updatePlayer(uid, playerId, "/companions", { [id]: null }, true);
- commit("REMOVE_COMPANION", { uid, playerId, id });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Deletes an existing player
- * A user can only delete their own player's so use uid from the store
- *
- * - Deletes player from the campaign it's in
- * - Deletes companions from the campaign (if the player had companions)
- * - Removes the player from character_control
- *
- * @param {string} id
- */
- async delete_player({ rootGetters, commit, dispatch }, id) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- const player = await dispatch("get_player", { uid, id });
-
- // Delete player from campaign
- if(player.campaing_id) {
- await dispatch("campaigns/delete_player",
- { id: player.campaign_id, playerId: id },
- { root: true }
- );
- }
-
- // Check if there were companions
- if(player.companions) {
- for(const companionId of Object.keys(player.companions)) {
- // Delete companion from campaign
- if(player.campaign_id) {
- await dispatch("campaigns/delete_companion",
- { id: player.campaign_id, companionId },
- { root: true }
- );
- }
-
- // Delete player_id from NPC
- await dispatch("npcs/update_npc_prop", {
- uid,
- id: companionId,
- property: "player_id",
- value: null
- }, { root: true });
- }
- }
-
- // Check if control over the character is given to a player
- await services.deletePlayer(uid, id, player.control);
-
- commit("REMOVE_PLAYER", id);
- commit("REMOVE_CACHED_PLAYER", { uid, id });
-
- const new_count = await services.updatePlayerCount(uid, -1);
- commit("SET_PLAYER_COUNT", new_count);
- dispatch("checkEncumbrance", "", { root: true });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Update character with data from Character Sync Extension
- *
- * @param {string} user_id
- * @param {string} id
- */
- async sync_player({ commit, dispatch }, { uid, id, sync_character }) {
- if(uid) {
- const services = await dispatch("get_player_services");
- const character = await getCharacterSyncCharacter(sync_character);
-
- const player = characterToPlayer(character);
- const search_player = convert_player(player);
-
- await services.syncPlayer(uid, id, player, search_player);
- commit("UPDATE_SEARCH_PLAYER", { id, search_player });
- commit("PATCH_CACHED_PLAYER", { uid, id, player });
- }
- },
-
- /**
- * Give control over a player to another user
- *
- * @param {string} user_id
- * @param {string} id
- */
- async give_out_control({ commit, dispatch, rootGetters }, { user_id, id }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- await services.updatePlayer(uid, id, "", { "control": user_id });
- await services.giveControl(uid, id, user_id);
- commit("SET_CONTROL", { uid, id, user_id });
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- /**
- * Removes the character control of a player
- * - control property on the player must be removed too
- *
- * @param {string} id playerId
- * @param {string} owner_id uid of the user who created the player
- */
- async remove_control({ commit, dispatch }, { uid, id, owner_id }) {
- if(uid) {
- const services = await dispatch("get_player_services");
- try {
- await services.updatePlayer(owner_id, id, "", { control: null });
- await services.removeControl(uid, id);
- commit("REMOVE_CHARACTER", id);
- return;
- } catch(error) {
- throw error;
- }
- }
- },
-
- clear_player_store({ commit, rootGetters }) {
- const uid = (rootGetters.user) ? rootGetters.user.uid : undefined;
- if(uid) {
- commit("CLEAR_STORE");
- }
- }
+ }
+ commit("SET_CHARACTERS", characters || {});
+ } catch (error) {
+ throw error;
+ }
+ }
+ return characters;
+ },
+
+ async get_owner_id({ dispatch }, { uid, playerId }) {
+ const services = await dispatch("get_player_services");
+ try {
+ const userId = await services.getOwner(uid, playerId);
+ return userId.user;
+ } catch (error) {
+ throw error;
+ }
+ },
+
+ /**
+ * Adds a newly created player for a user
+ * A user can only add players for themselves so we use the uid from the store
+ *
+ * @param {object} player
+ * @returns {string} the id of the newly added player
+ */
+ async add_player({ rootGetters, commit, dispatch }, player) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ const available_slots = rootGetters.tier.benefits.players;
+
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ const used_slots = await services.getPlayerCount(uid);
+
+ if (used_slots >= available_slots) {
+ throw "Not enough slots";
+ }
+ try {
+ player = filterUnusedAttributes(player);
+ player = parseInts(player);
+
+ if (player.skills) {
+ player.skills = filterSkills(player.skills);
+ }
+
+ const search_player = convert_player(player);
+ const id = await services.addPlayer(uid, player, search_player);
+
+ // If there are companions, save the playerId in the NPC (So the player can edit this specific NPC)
+ if (player.companions) {
+ for (const npcId of Object.keys(player.companions)) {
+ await dispatch(
+ "npcs/update_npc_prop",
+ {
+ uid,
+ id: npcId,
+ property: "player_id",
+ value: id,
+ },
+ { root: true }
+ );
+ }
+ }
+ commit("SET_PLAYER", { id, search_player });
+ commit("SET_CACHED_PLAYER", { uid, id, player });
+
+ const new_count = await services.updatePlayerCount(uid, 1);
+ commit("SET_PLAYER_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return id;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Overwrites and existing player
+ * It is possible to edit the player of another user (for companions)
+ * therefore we send the uid from where the function is called
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {object} player
+ */
+ async edit_player(
+ { commit, dispatch },
+ { uid, id, player, companions = [], deleted_companions = [] }
+ ) {
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ player = filterUnusedAttributes(player);
+ player = parseInts(player);
+
+ if (player.skills) {
+ player.skills = filterSkills(player.skills);
+ }
+
+ const search_player = convert_player(player);
+ await services.editPlayer(uid, id, player, search_player);
+
+ // If there are companions, save the playerId in the NPC (So the player can edit this specific NPC)
+ for (const companion of companions) {
+ await dispatch(
+ "npcs/update_npc_prop",
+ {
+ uid,
+ id: companion.key,
+ property: "player_id",
+ value: id,
+ },
+ { root: true }
+ );
+
+ // If the player is in a campaign, update the companion's curHp in the campaign
+ if (player.campaign_id) {
+ await dispatch(
+ "campaigns/update_companion",
+ {
+ uid,
+ id: player.campaign_id,
+ companionId: companion.key,
+ property: "curHp",
+ value: companion.hit_points,
+ },
+ { root: true }
+ );
+ }
+ }
+
+ // Remove the player_id from an NPC if that NPC was removed as a companion
+ for (const companionId of deleted_companions) {
+ await dispatch(
+ "npcs/update_npc_prop",
+ {
+ uid,
+ id: companionId,
+ property: "player_id",
+ value: null,
+ },
+ { root: true }
+ );
+
+ // If the player is in a campaign, delete the companion from the campaign
+ if (player.campaign_id) {
+ await dispatch(
+ "campaigns/delete_companion",
+ {
+ id: player.campaign_id,
+ companionId,
+ },
+ { root: true }
+ );
+ }
+ }
+
+ commit("SET_PLAYER", { id, search_player });
+ commit("SET_CACHED_PLAYER", { uid, id, player });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Sets the property of a player
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {number} value
+ */
+ async set_player_prop({ commit, dispatch }, { uid, id, property, value }) {
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ const update_search = ["character_name", "avatar", "campaign_id", "sync_character"].includes(
+ property
+ );
+ try {
+ value = numberValues.includes(value) && value !== null ? parseInt(value) : value;
+ await services.updatePlayer(uid, id, "", { [property]: value }, update_search);
+ commit("SET_PLAYER_PROP", { uid, id, property, value, update_search });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Removes the companion link from a player
+ *
+ * @param {string} uid
+ * @param {string} playerId
+ * @param {string} id companionId
+ */
+ async delete_companion({ commit, dispatch }, { uid, playerId, id }) {
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ await services.updatePlayer(uid, playerId, "/companions", { [id]: null }, true);
+ commit("REMOVE_COMPANION", { uid, playerId, id });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes an existing player
+ * A user can only delete their own player's so use uid from the store
+ *
+ * - Deletes player from the campaign it's in
+ * - Deletes companions from the campaign (if the player had companions)
+ * - Removes the player from character_control
+ *
+ * @param {string} id
+ */
+ async delete_player({ rootGetters, commit, dispatch }, id) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ const player = await dispatch("get_player", { uid, id });
+
+ // Delete player from campaign
+ if (player.campaing_id) {
+ await dispatch(
+ "campaigns/delete_player",
+ { id: player.campaign_id, playerId: id },
+ { root: true }
+ );
+ }
+
+ // Check if there were companions
+ if (player.companions) {
+ for (const companionId of Object.keys(player.companions)) {
+ // Delete companion from campaign
+ if (player.campaign_id) {
+ await dispatch(
+ "campaigns/delete_companion",
+ { id: player.campaign_id, companionId },
+ { root: true }
+ );
+ }
+
+ // Delete player_id from NPC
+ await dispatch(
+ "npcs/update_npc_prop",
+ {
+ uid,
+ id: companionId,
+ property: "player_id",
+ value: null,
+ },
+ { root: true }
+ );
+ }
+ }
+
+ // Check if control over the character is given to a player
+ await services.deletePlayer(uid, id, player.control);
+
+ commit("REMOVE_PLAYER", id);
+ commit("REMOVE_CACHED_PLAYER", { uid, id });
+
+ const new_count = await services.updatePlayerCount(uid, -1);
+ commit("SET_PLAYER_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Update character with data from Character Sync Extension
+ *
+ * @param {string} user_id
+ * @param {string} id
+ */
+ async sync_player({ commit, dispatch }, { uid, id, sync_character }) {
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ const character = await getCharacterSyncCharacter(sync_character);
+
+ const player = characterToPlayer(character);
+ const search_player = convert_player(player);
+
+ await services.syncPlayer(uid, id, player, search_player);
+ commit("UPDATE_SEARCH_PLAYER", { id, search_player });
+ commit("PATCH_CACHED_PLAYER", { uid, id, player });
+ }
+ },
+
+ /**
+ * Give control over a player to another user
+ *
+ * @param {string} user_id
+ * @param {string} id
+ */
+ async give_out_control({ commit, dispatch, rootGetters }, { user_id, id }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ await services.updatePlayer(uid, id, "", { control: user_id });
+ await services.giveControl(uid, id, user_id);
+ commit("SET_CONTROL", { uid, id, user_id });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Removes the character control of a player
+ * - control property on the player must be removed too
+ *
+ * @param {string} id playerId
+ * @param {string} owner_id uid of the user who created the player
+ */
+ async remove_control({ commit, dispatch }, { uid, id, owner_id }) {
+ if (uid) {
+ const services = await dispatch("get_player_services");
+ try {
+ await services.updatePlayer(owner_id, id, "", { control: null });
+ await services.removeControl(uid, id);
+ commit("REMOVE_CHARACTER", id);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ clear_player_store({ commit, rootGetters }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ commit("CLEAR_STORE");
+ }
+ },
};
const player_mutations = {
- SET_PLAYER_SERVICES(state, payload) { Vue.set(state, "player_services", payload); },
- SET_PLAYERS(state, payload) { Vue.set(state, "players", payload); },
- SET_CHARACTERS(state, payload) { Vue.set(state, "characters", payload); },
- SET_PLAYER_COUNT(state, value) { Vue.set(state, "player_count", value); },
- SET_PLAYER(state, { id, search_player }) {
- if(state.players) {
- Vue.set(state.players, id, search_player);
- } else {
- Vue.set(state, "players", { [id]: search_player });
- }
- },
- UPDATE_SEARCH_PLAYER(state, { id, search_player }) {
- if(state.players && state.players[id]) {
- Vue.set(state.players, id, {...state.players[id], ...search_player });
- }
- },
- REMOVE_PLAYER(state, id) {
- Vue.delete(state.players, id);
- },
- REMOVE_CHARACTER(state, id) {
- if(state.characters) {
- Vue.delete(state.characters, id);
- }
- },
- SET_CACHED_PLAYER(state, { uid, id, player }) {
- if(state.cached_players[uid]) {
- Vue.set(state.cached_players[uid], id, player);
- } else {
- Vue.set(state.cached_players, uid, { [id]: player });
- }
- },
- PATCH_CACHED_PLAYER(state, { uid, id, player }) {
- if(state.cached_players[uid]) {
- Vue.set(state.cached_players[uid], id, {...state.cached_players[uid][id], ...player });
- }
- },
- REMOVE_CACHED_PLAYER(state, { uid, id }) {
- if(state.cached_players[uid]) {
- Vue.delete(state.cached_players[uid], id);
- }
- },
- SET_PLAYER_PROP(state, { uid, id, property, value, update_search }) {
- if(state.cached_players[uid] && state.cached_players[uid][id]) {
- Vue.set(state.cached_players[uid][id], property, value);
- }
- if(update_search && state.players && state.players[id]) {
- Vue.set(state.players[id], property, value);
- }
- },
- SET_CONTROL(state, { uid, id, user_id }) {
- Vue.set(state.cached_players[uid][id], "control", user_id);
- },
- REMOVE_COMPANION(state, { uid, playerId, id }) {
- if(state.cached_players[uid] && state.cached_players[uid][playerId] && state.cached_players[uid][playerId].campanions) {
- Vue.delete(state.cached_players[uid][playerId].campanions, id);
- }
- if(state.players && state.players[playerId] && state.players[playerId].companions) {
- Vue.delete(state.players[playerId].companions, id);
- }
- },
- CLEAR_STORE(state) {
- Vue.set(state, "players", undefined);
- Vue.set(state, "player_count", 0);
- Vue.set(state, "characters", undefined);
- }
+ SET_PLAYER_SERVICES(state, payload) {
+ Vue.set(state, "player_services", payload);
+ },
+ SET_PLAYERS(state, payload) {
+ Vue.set(state, "players", payload);
+ },
+ SET_CHARACTERS(state, payload) {
+ Vue.set(state, "characters", payload);
+ },
+ SET_PLAYER_COUNT(state, value) {
+ Vue.set(state, "player_count", value);
+ },
+ SET_PLAYER(state, { id, search_player }) {
+ if (state.players) {
+ Vue.set(state.players, id, search_player);
+ } else {
+ Vue.set(state, "players", { [id]: search_player });
+ }
+ },
+ UPDATE_SEARCH_PLAYER(state, { id, search_player }) {
+ if (state.players && state.players[id]) {
+ Vue.set(state.players, id, { ...state.players[id], ...search_player });
+ }
+ },
+ REMOVE_PLAYER(state, id) {
+ Vue.delete(state.players, id);
+ },
+ REMOVE_CHARACTER(state, id) {
+ if (state.characters) {
+ Vue.delete(state.characters, id);
+ }
+ },
+ SET_CACHED_PLAYER(state, { uid, id, player }) {
+ if (state.cached_players[uid]) {
+ Vue.set(state.cached_players[uid], id, player);
+ } else {
+ Vue.set(state.cached_players, uid, { [id]: player });
+ }
+ },
+ PATCH_CACHED_PLAYER(state, { uid, id, player }) {
+ if (state.cached_players[uid]) {
+ Vue.set(state.cached_players[uid], id, { ...state.cached_players[uid][id], ...player });
+ }
+ },
+ REMOVE_CACHED_PLAYER(state, { uid, id }) {
+ if (state.cached_players[uid]) {
+ Vue.delete(state.cached_players[uid], id);
+ }
+ },
+ SET_PLAYER_PROP(state, { uid, id, property, value, update_search }) {
+ if (state.cached_players[uid] && state.cached_players[uid][id]) {
+ Vue.set(state.cached_players[uid][id], property, value);
+ }
+ if (update_search && state.players && state.players[id]) {
+ Vue.set(state.players[id], property, value);
+ }
+ },
+ SET_CONTROL(state, { uid, id, user_id }) {
+ Vue.set(state.cached_players[uid][id], "control", user_id);
+ },
+ REMOVE_COMPANION(state, { uid, playerId, id }) {
+ if (
+ state.cached_players[uid] &&
+ state.cached_players[uid][playerId] &&
+ state.cached_players[uid][playerId].campanions
+ ) {
+ Vue.delete(state.cached_players[uid][playerId].campanions, id);
+ }
+ if (state.players && state.players[playerId] && state.players[playerId].companions) {
+ Vue.delete(state.players[playerId].companions, id);
+ }
+ },
+ CLEAR_STORE(state) {
+ Vue.set(state, "players", undefined);
+ Vue.set(state, "player_count", 0);
+ Vue.set(state, "characters", undefined);
+ },
};
export default {
- namespaced: true,
- state: player_state,
- getters: player_getters,
- actions: player_actions,
- mutations: player_mutations
-}
+ namespaced: true,
+ state: player_state,
+ getters: player_getters,
+ actions: player_actions,
+ mutations: player_mutations,
+};
diff --git a/src/store/modules/userContent/spells.js b/src/store/modules/userContent/spells.js
new file mode 100644
index 000000000..137a31ea2
--- /dev/null
+++ b/src/store/modules/userContent/spells.js
@@ -0,0 +1,242 @@
+import Vue from "vue";
+import { SpellServices } from "src/services/spells";
+import _ from "lodash";
+
+// Converts a full spell to a search_spell
+const convert_spell = (spell) => {
+ const properties = ["name", "school", "level"];
+ const returnSpell = {};
+
+ for (const prop of properties) {
+ if (spell.hasOwnProperty(prop)) {
+ returnSpell[prop] = prop === "name" ? spell[prop].toLowerCase() : spell[prop];
+ }
+ }
+ return returnSpell;
+};
+
+const spell_state = () => ({
+ spell_services: null,
+ cached_spells: {},
+ spell_count: 0,
+ spells: undefined,
+});
+
+const spell_getters = {
+ spells: (state) => {
+ // Convert object to sorted array
+ return _.chain(state.spells)
+ .filter((spell, key) => {
+ spell.key = key;
+ return spell;
+ })
+ .orderBy("name", "asc")
+ .value();
+ },
+ spell_services: (state) => {
+ return state.spell_services;
+ },
+ spell_count: (state) => {
+ return state.spell_count;
+ },
+};
+
+const spell_actions = {
+ async get_spell_services({ getters, commit }) {
+ if (getters.spell_services === null || !Object.keys(getters.spell_services).length) {
+ commit("SET_SPELL_SERVICES", new SpellServices());
+ }
+ return getters.spell_services;
+ },
+
+ /**
+ * Get all the spells for a user from 'search_spells'
+ * Stores those spells in spells store
+ * Returns an Array of spells, ordered by name.
+ */
+ async get_spells({ state, rootGetters, dispatch, commit }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ let spells = state.spells ? state.spells : undefined;
+
+ if (!spells && uid) {
+ const services = await dispatch("get_spell_services");
+ try {
+ spells = await services.getSpells(uid);
+ commit("SET_SPELLS", spells || {});
+ } catch (error) {
+ throw error;
+ }
+ }
+ return spells;
+ },
+
+ /**
+ * Fetches the total count of spells for a user
+ * Stores the count it in spell_count
+ */
+ async fetch_spell_count({ rootGetters, commit, dispatch }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_spell_services");
+ try {
+ let count = (await services.getSpellCount(uid)) || 0;
+ commit("SET_SPELL_COUNT", count);
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ async get_spell({ state, commit, dispatch }, { uid, id }) {
+ let spell = state.cached_spells[uid] ? state.cached_spells[uid][id] : undefined;
+
+ // The spell is not in the store and needs to be fetched from the database
+ if (!spell) {
+ const services = await dispatch("get_spell_services");
+ try {
+ spell = await services.getSpell(uid, id);
+ commit("SET_CACHED_SPELL", { uid, id, spell });
+ } catch (error) {
+ throw error;
+ }
+ }
+ return spell;
+ },
+
+ /**
+ * Adds a newly created SPELL for a user
+ * A user can only add SPELLS for themselves so we use the uid from the store
+ *
+ * @param {object} spell
+ * @returns {string} the id of the newly added spell
+ */
+ async add_spell({ rootGetters, commit, dispatch }, spell) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ const available_slots = rootGetters.tier.benefits.spells;
+
+ if (uid) {
+ const services = await dispatch("get_spell_services");
+ const used_slots = await services.getSpellCount(uid);
+
+ if (used_slots >= available_slots) {
+ return "Not enough slots";
+ }
+ try {
+ const search_spell = convert_spell(spell);
+ const id = await services.addSpell(uid, spell, search_spell);
+ commit("SET_SPELL", { id, search_spell });
+ commit("SET_CACHED_SPELL", { uid, id, spell });
+
+ const new_count = await services.updateSpellCount(uid, 1);
+ commit("SET_SPELL_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return id;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Updates and existing spell
+ * A user can only edit their own spells so use uid from the store
+ *
+ * @param {string} uid
+ * @param {string} id
+ * @param {object} spell
+ */
+ async edit_spell({ rootGetters, commit, dispatch }, { id, spell }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_spell_services");
+ try {
+ const search_spell = convert_spell(spell);
+ await services.editSpell(uid, id, spell, search_spell);
+ commit("SET_SPELL", { id, search_spell });
+ commit("SET_CACHED_SPELL", { uid, id, spell });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ /**
+ * Deletes an existing spell
+ * A user can only delete their own spells so use uid from the store
+ *
+ * @param {string} id
+ */
+ async delete_spell({ rootGetters, commit, dispatch }, id) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ const services = await dispatch("get_spell_services");
+ try {
+ await services.deleteSpell(uid, id);
+ commit("REMOVE_SPELL", id);
+ commit("REMOVE_CACHED_SPELL", { uid, id });
+
+ const new_count = await services.updateSpellCount(uid, -1);
+ commit("SET_SPELL_COUNT", new_count);
+ dispatch("checkEncumbrance", "", { root: true });
+ return;
+ } catch (error) {
+ throw error;
+ }
+ }
+ },
+
+ clear_spell_store({ commit, rootGetters }) {
+ const uid = rootGetters.user ? rootGetters.user.uid : undefined;
+ if (uid) {
+ commit("CLEAR_STORE");
+ }
+ },
+};
+
+const spell_mutations = {
+ SET_SPELL_SERVICES(state, payload) {
+ Vue.set(state, "spell_services", payload);
+ },
+ SET_SPELL_COUNT(state, value) {
+ Vue.set(state, "spell_count", value);
+ },
+ SET_SPELLS(state, value) {
+ Vue.set(state, "spells", value);
+ },
+ SET_CACHED_SPELL(state, { uid, id, spell }) {
+ if (state.cached_spells[uid]) {
+ Vue.set(state.cached_spells[uid], id, spell);
+ } else {
+ Vue.set(state.cached_spells, uid, { [id]: spell });
+ }
+ },
+ SET_SPELL(state, { id, search_spell }) {
+ if (state.spells) {
+ Vue.set(state.spells, id, search_spell);
+ } else {
+ Vue.set(state, "spells", { [id]: search_spell });
+ }
+ },
+ REMOVE_SPELL(state, id) {
+ Vue.delete(state.spells, id);
+ },
+ REMOVE_CACHED_SPELL(state, { uid, id }) {
+ if (state.cached_spells[uid]) {
+ Vue.delete(state.cached_spells[uid], id);
+ }
+ },
+ CLEAR_STORE(state) {
+ Vue.set(state, "spells", undefined);
+ Vue.set(state, "spell_count", 0);
+ },
+};
+
+export default {
+ namespaced: true,
+ state: spell_state,
+ getters: spell_getters,
+ actions: spell_actions,
+ mutations: spell_mutations,
+};
diff --git a/src/utils/actionConstants.js b/src/utils/actionConstants.js
new file mode 100644
index 000000000..c0996956c
--- /dev/null
+++ b/src/utils/actionConstants.js
@@ -0,0 +1,49 @@
+export const aoe_types = Object.freeze([
+ { label: "None", value: "none" },
+ { label: "Cone", value: "cone" },
+ { label: "Cube", value: "cube" },
+ { label: "Cylinder", value: "cylinder" },
+ { label: "Line", value: "line" },
+ { label: "Radius", value: "radius" },
+ { label: "Sphere", value: "sphere" },
+ { label: "Square", value: "square" },
+ { label: "Square Feet", value: "square feet" },
+]);
+
+export const attack_types = Object.freeze({
+ melee_weapon: {
+ label: "Melee weapon",
+ value: "melee_weapon",
+ hint: "A melee weapon attack",
+ },
+ ranged_weapon: {
+ label: "Ranged weapon",
+ value: "ranged_weapon",
+ hint: "A ranged weapon attack",
+ },
+ spell_attack: {
+ label: "Spell attack",
+ value: "spell_attack",
+ hint: "A spell attack that has to hit",
+ },
+ save: {
+ label: "Save",
+ value: "save",
+ hint: "An attack that requires a saving throw",
+ },
+ damage: {
+ label: "Damage",
+ value: "damage",
+ hint: "Damage without a to hit or saving throw",
+ },
+ healing: {
+ label: "Healing",
+ value: "healing",
+ hint: "Restores hit points to a target",
+ },
+ other: {
+ label: "Other",
+ value: "other",
+ hint: "An action without damage or healing",
+ },
+});
diff --git a/src/utils/generalConstants.js b/src/utils/generalConstants.js
index dc122ee9e..4fc092e7e 100644
--- a/src/utils/generalConstants.js
+++ b/src/utils/generalConstants.js
@@ -70,34 +70,40 @@ export const abilities = Object.freeze([
]);
export const damage_types = Object.freeze([
- "acid",
+ "non_magical_bludgeoning",
+ "non_magical_piercing",
+ "non_magical_slashing",
"bludgeoning",
+ "piercing",
+ "slashing",
+ "acid",
"cold",
"fire",
"force",
"lightning",
"necrotic",
- "piercing",
"poison",
"psychic",
"radiant",
- "slashing",
"thunder",
]);
export const damage_type_icons = Object.freeze({
- acid: "fas fa-tint",
+ non_magical_bludgeoning: "fas fa-hammer-war",
+ non_magical_piercing: "far fa-bow-arrow",
+ non_magical_slashing: "fas fa-sword",
bludgeoning: "fas fa-hammer-war",
+ piercing: "far fa-bow-arrow",
+ slashing: "fas fa-sword",
+ acid: "fas fa-tint",
cold: "far fa-snowflake",
fire: "fas fa-flame",
force: "fas fa-sparkles",
lightning: "fas fa-bolt",
necrotic: "fas fa-skull",
- piercing: "far fa-bow-arrow",
poison: "fas fa-flask-poison",
psychic: "fas fa-brain",
radiant: "fas fa-sun",
- slashing: "fas fa-sword",
thunder: "far fa-waveform-path",
});
diff --git a/src/utils/generalFunctions.js b/src/utils/generalFunctions.js
index 660a5c23e..276874efe 100644
--- a/src/utils/generalFunctions.js
+++ b/src/utils/generalFunctions.js
@@ -119,39 +119,54 @@ export function makeDate(input, showTime = false, short = false) {
/**
* Turns a character object from D&D Character Sync into a HK player object
- *
+ *
* @param {object} character
* @returns {object} player
*/
export function characterToPlayer(character) {
const player = {};
- if(character.armor_class !== undefined) player.ac = parseInt(character.armor_class.between(1, 99));
- if(character.avatar !== undefined) player.avatar = (character.avatar.length <= 2000) ? character.avatar : character.avatar.subString(0, 2000);
- if(character.name !== undefined) player.character_name = (character.name.length <= 100) ? character.name : character.name.subString(0, 100);
- if(character.strength !== undefined) player.strength = parseInt(character.strength.between(1, 99));
- if(character.dexterity !== undefined) player.dexterity = parseInt(character.dexterity.between(1, 99));
- if(character.constitution !== undefined) player.constitution = parseInt(character.constitution.between(1, 99));
- if(character.intelligence !== undefined) player.intelligence = parseInt(character.intelligence.between(1, 99));
- if(character.level !== undefined) player.level = parseInt(character.level.between(1, 20));
- if(character.max_hit_points !== undefined) player.maxHp = parseInt(character.max_hit_points.between(1, 999));
- if(character.walking_speed !== undefined) player.speed = parseInt(character.walking_speed.between(0, 999));
- if(character.initiative !== undefined) player.initiative = parseInt(character.initiative.between(-10, 99));
+ if (character.armor_class != undefined && character.armor_class !== "")
+ player.ac = parseInt(character.armor_class.between(1, 99));
+ if (character.avatar != undefined && character.avatar !== "")
+ player.avatar =
+ character.avatar.length <= 2000 ? character.avatar : character.avatar.subString(0, 2000);
+ if (character.name != undefined && character.name !== "")
+ player.character_name =
+ character.name.length <= 100 ? character.name : character.name.subString(0, 100);
+ if (character.strength != undefined && character.strength !== "")
+ player.strength = parseInt(character.strength.between(1, 99));
+ if (character.dexterity != undefined && character.dexterity !== "")
+ player.dexterity = parseInt(character.dexterity.between(1, 99));
+ if (character.constitution != undefined && character.constitution !== "")
+ player.constitution = parseInt(character.constitution.between(1, 99));
+ if (character.intelligence != undefined && character.intelligence !== "")
+ player.intelligence = parseInt(character.intelligence.between(1, 99));
+ if (character.xp != undefined && character.xp !== "")
+ player.experience = parseInt(character.xp.between(0, 355000));
+ else if (character.level != undefined && character.level !== "")
+ player.level = parseInt(character.level.between(1, 20));
+ if (character.max_hit_points != undefined && character.max_hit_points !== "")
+ player.maxHp = parseInt(character.max_hit_points.between(1, 999));
+ if (character.walking_speed != undefined && character.walking_speed !== "")
+ player.speed = parseInt(character.walking_speed.between(0, 999));
+ if (character.initiative != undefined && character.initiative !== "")
+ player.initiative = parseInt(character.initiative.between(-10, 99));
return player;
}
/**
* Compares player with linked character
- *
+ *
* @param {object} player
* @returns {boolean}
*/
export function comparePlayerToCharacter(sync_character, player) {
- const character = characterToPlayer(sync_character);
+ const character = sync_character ? characterToPlayer(sync_character) : {};
const compare_player = {};
- for(const key of Object.keys(character)) {
+ for (const key of Object.keys(character)) {
compare_player[key] = player[key];
}
return _.isEqual(compare_player, character);
@@ -159,22 +174,18 @@ export function comparePlayerToCharacter(sync_character, player) {
/**
* Check if the "D&D Character Sync" extension is installed
- *
- * @param {string} url
+ *
+ * @param {string} url
*/
export async function extensionInstalled() {
return new Promise((resolve) => {
- chrome.runtime.sendMessage(
- character_sync_id,
- { request_content: ["version"] },
- (response) => {
- if (response) {
- resolve(response.version)
- } else {
- return undefined;
- }
+ chrome.runtime.sendMessage(character_sync_id, { request_content: ["version"] }, (response) => {
+ if (response) {
+ resolve(response.version);
+ } else {
+ return undefined;
}
- );
+ });
});
}
@@ -190,19 +201,18 @@ export async function getCharacterSyncStorage() {
if (response && response.characters) {
resolve(response.characters);
} else {
- reject('Something went wrong getting data from Character Sync extension.');
+ reject("Something went wrong getting data from Character Sync extension.");
}
}
);
});
}
-
/**
* Get a single character from the "D&D Character Sync" Chrome Extension
- *
- * @param {string} url
- * @returns
+ *
+ * @param {string} url
+ * @returns
*/
export async function getCharacterSyncCharacter(url) {
return new Promise((resolve, reject) => {
@@ -218,4 +228,4 @@ export async function getCharacterSyncCharacter(url) {
}
);
});
-}
\ No newline at end of file
+}
diff --git a/src/utils/spellConstants.js b/src/utils/spellConstants.js
new file mode 100644
index 000000000..c64c23f07
--- /dev/null
+++ b/src/utils/spellConstants.js
@@ -0,0 +1,85 @@
+export const spell_levels = Object.freeze([
+ { value: 0, label: "Cantrip" },
+ { value: 1, label: "1st" },
+ { value: 2, label: "2nd" },
+ { value: 3, label: "3rd" },
+ { value: 4, label: "4th" },
+ { value: 5, label: "5th" },
+ { value: 5, label: "6th" },
+ { value: 7, label: "7th" },
+ { value: 8, label: "8th" },
+ { value: 9, label: "9th" },
+]);
+
+export const spell_schools = Object.freeze([
+ { label: "Abjuration", value: "abjuration" },
+ { label: "Conjuration", value: "conjuration" },
+ { label: "Divination", value: "divination" },
+ { label: "Enchantment", value: "enchantment" },
+ { label: "Evocation", value: "evocation" },
+ { label: "Illusion", value: "illusion" },
+ { label: "Necromancy", value: "necromancy" },
+ { label: "Transmutation", value: "transmutation" },
+]);
+
+export const spell_components = Object.freeze([
+ { label: "Verbal", value: "verbal" },
+ { label: "Somatic", value: "somatic" },
+ { label: "Material", value: "material" },
+]);
+
+export const spell_cast_time_types = Object.freeze([
+ { label: "Action", value: "action" },
+ { label: "Bonus Action", value: "bonus_action" },
+ { label: "Reaction", value: "reaction" },
+ { label: "Minute", value: "minute" },
+ { label: "Hour", value: "hour" },
+ { label: "No Action", value: "no_action" },
+ { label: "Special", value: "special" },
+]);
+
+export const spell_range_types = Object.freeze([
+ { label: "Self", value: "self" },
+ { label: "Touch", value: "touch" },
+ { label: "Ranged", value: "ranged" },
+ { label: "Sight", value: "sight" },
+ { label: "Unlimited", value: "unlimited" },
+ { label: "Special", value: "special" },
+]);
+
+export const spell_duration_types = Object.freeze([
+ { label: "Concentration", value: "concentration" },
+ { label: "Instantaneous", value: "instantaneous" },
+ { label: "Special", value: "special" },
+ { label: "Time", value: "time" },
+ { label: "Until Dispelled", value: "until_dispelled" },
+ { label: "Until Dispelled or Triggered", value: "until_dispelled_or_triggered" },
+]);
+
+export const spell_duration_types_time = Object.freeze(["concentration", "time"]);
+
+export const spell_duration_times = Object.freeze([
+ { label: "Round", value: "round" },
+ { label: "Minute", value: "minute" },
+ { label: "Hour", value: "hour" },
+ { label: "Day", value: "day" },
+]);
+
+export const level_scaling = Object.freeze([
+ { label: "None", value: "none" },
+ { label: "Character Level", value: "character_level" },
+ { label: "Spell Scale", value: "spell_scale" },
+ { label: "Spell Level", value: "spell_level" },
+]);
+
+export default {
+ spell_levels,
+ spell_schools,
+ spell_components,
+ spell_cast_time_types,
+ spell_range_types,
+ spell_duration_types,
+ spell_duration_types_time,
+ spell_duration_times,
+ level_scaling,
+};
diff --git a/src/utils/spellFunctions.js b/src/utils/spellFunctions.js
new file mode 100644
index 000000000..3dbe12d13
--- /dev/null
+++ b/src/utils/spellFunctions.js
@@ -0,0 +1,102 @@
+import numeral from "numeral";
+
+/**
+ * Generates description for each level tier for spell level scaling
+ *
+ * @param {object[]} tiers
+ * @param {string} scaling character_level | spell_scale | spell_level
+ * @param {number} level base spell level
+ *
+ * @returns {string}
+ * */
+export function spellScalingDescription(tiers, scaling, level, dice_type) {
+ let description = [];
+
+ // CHARACTER LEVEL
+ if (scaling === "character_level") {
+ description = getDescriptionForSpellScalingCharacterLevel(tiers, dice_type);
+ }
+
+ // SPELL SCALE
+ else if (scaling === "spell_scale") {
+ description = getDescriptionForSpellScaling(tiers, level, dice_type);
+ }
+
+ // SPELL LEVEL
+ else if (scaling === "spell_level") {
+ description = getDescriptionForSpellLevel(tiers, dice_type);
+ }
+ return description.join("\n");
+}
+
+function getDescriptionForSpellScalingCharacterLevel(tiers, dice_type) {
+ const description = [
+ "This spell's damage/projectiles increases when your character reaches a higher level.",
+ ];
+ for (let tier of tiers) {
+ let count_txt = `${tier.projectile_count} projectile${tier.projectile_count > 1 ? "s" : ""}`;
+ let level_txt = `at ${numeral(tier.level).format("0o")} level`;
+ let damage_txt = "this spell roll does ";
+ damage_txt +=
+ tier.dice_count || dice_type ? `${tier.dice_count || "..."}d${dice_type || "..."}` : "";
+
+ if (tier.fixed_val) {
+ damage_txt += `${tier.dice_count || dice_type ? "+" : ""}${tier.fixed_val || ""}`;
+ }
+
+ let new_line = `${tier.projectile_count ? count_txt : ""} `;
+ new_line += `${
+ !tier.projectile_count && tier.dice_count ? level_txt.capitalize() + "s," : level_txt
+ }`;
+ new_line += `${tier.projectile_count && tier.dice_count ? ", and " : "."}`;
+ new_line += `${tier.dice_count ? damage_txt : ""}`;
+ description.push(new_line);
+ }
+
+ return description;
+}
+
+function getDescriptionForSpellScaling(tiers, level, dice_type) {
+ let tier = tiers[0];
+ // Opening line
+ let level_txt = "When you cast this spell using a spell slot of ";
+ level_txt += `${numeral(parseInt(level) + 1).format("0o")} level or higher,`;
+
+ // Damage modifier text
+ let damage_txt = "the damage of this roll increases by ";
+ damage_txt +=
+ tier.dice_count || dice_type ? `${tier.dice_count || "..."}d${dice_type || "..."}` : "";
+
+ if (tier.fixed_val) {
+ damage_txt += `${tier.dice_count || dice_type ? "+" : ""}${tier.fixed_val || ""}`;
+ }
+
+ // Projectile count text
+ let count_txt = `the spell creates ${tier.projectile_count} more projectile${
+ tier.projectile_count > 1 ? "s" : ""
+ }`;
+ // Spell slot text
+ let slot_txt = `for ${
+ tier.level < 2 ? "each slot level" : "every " + tier.level + " slot levels"
+ } above ${numeral(level).format("0o")}.`;
+
+ let text = `${level_txt} ${tier.projectile_count ? count_txt : ""} ${
+ tier.projectile_count && tier.dice_count ? "and " : ""
+ }${tier.dice_count ? damage_txt : ""} ${slot_txt}`;
+ return [text];
+}
+
+function getDescriptionForSpellLevel(tiers, dice_type) {
+ const description = [];
+ for (let tier of tiers) {
+ let new_line = "When you cast this spell using a ";
+ new_line += `${numeral(tier.level).format("0o")}-level spell slot, this spell roll does `;
+ new_line += `${tier.dice_count || "..."}d${dice_type || "..."}${tier.fixed_val ? "+" : ""}${
+ tier.fixed_val || ""
+ } damage.`;
+
+ description.push(new_line);
+ }
+
+ return description;
+}
diff --git a/src/views/Admin/ExportDatabase.vue b/src/views/Admin/ExportDatabase.vue
index 854ddfb7d..861522f75 100644
--- a/src/views/Admin/ExportDatabase.vue
+++ b/src/views/Admin/ExportDatabase.vue
@@ -1,22 +1,14 @@
-
- Creates a JSON file with an array of all entries in a firebase reference.
-
+
Creates a JSON file with an array of all entries in a firebase reference.
- ".key" is saved under "_id" for mongodb import.
- "metadata" is deleted.
- "changed" is deleted.
- "url" is generated, kebap-lowercase-name.
-
+
{{ ref ? `Download ${ref}` : "Select a reference" }}
@@ -29,67 +21,84 @@
\ No newline at end of file
+.select {
+ max-width: 400px;
+ margin-bottom: 20px;
+}
+
diff --git a/src/views/Admin/GenerateXML.vue b/src/views/Admin/GenerateXML.vue
index 4bc8cb52b..2c5d2d186 100644
--- a/src/views/Admin/GenerateXML.vue
+++ b/src/views/Admin/GenerateXML.vue
@@ -39,6 +39,8 @@ export default {
"https://harmlesskey.com/tools/encounter-builder/build-encounter",
"https://harmlesskey.com/tools/monster-creator",
"https://harmlesskey.com/tools/monster-creator/create-monster",
+ "https://harmlesskey.com/tools/spell-creator",
+ "https://harmlesskey.com/tools/spell-creator/create-spell",
"https://harmlesskey.com/tools/character-builder",
],
};
diff --git a/src/views/Compendium/Monsters.vue b/src/views/Compendium/Monsters.vue
index a3af41ec2..dac91371f 100644
--- a/src/views/Compendium/Monsters.vue
+++ b/src/views/Compendium/Monsters.vue
@@ -7,72 +7,26 @@
-
-
-
-
-
-
-
-
-
-
- {{ types[0] }}
-
-
- (+{{ types.length-1 }})
-
-
-
-
-
-
-
-
- {{ challenge_rating[0] }}
-
-
- (+{{ challenge_rating.length-1 }})
-
-
-
-
-
- {{
- (scope.opt == 0.125) ? "1/8" :
- (scope.opt == 0.25) ? "1/4" :
- (scope.opt == 0.5) ? "1/2" :
- scope.opt
- }}
-
-
-
-
-
-
- Filter
-
-
+
+
+
+
+
+ Filter
+
+
+
+
Nothing found
@@ -81,8 +35,8 @@
with a type of {{ types.join(" or ")}}
-
- {{ types ? "and a" : "with a" }} CR of {{ challenge_rating.join(" or ")}}
+
+ {{ types ? "and a" : "with a" }} CR between {{ cr.min }} and {{ cr.max }}
@@ -146,6 +100,41 @@
+
+
+
+
+
+
+ Challenge rating
+
+
+
+
+
@@ -153,6 +142,7 @@
import ViewMonster from "src/components/compendium/Monster.vue";
import { monsterMixin } from "src/mixins/monster.js";
import { mapActions } from "vuex";
+ import _ from "lodash";
export default {
name: "Monsters",
@@ -163,10 +153,12 @@
data() {
return {
monsters: [],
+ filter_dialog: false,
+ filter: {},
search: "",
query: null,
- challenge_rating: [],
- types: null,
+ cr: { min: 0, max: 30 },
+ types: [],
pagination: {
sortBy: "name",
descending: false,
@@ -196,7 +188,7 @@
field: "challenge_rating",
align: "left",
sortable: true,
- format: val => this.cr(val)
+ format: val => this.cr_label(val)
}
],
loading: true,
@@ -209,31 +201,53 @@
crs.push(Number(cr));
}
return crs.sort(function(a, b){return a-b});
+ },
+ type_options() {
+ return this.monster_types.map(type => { return { label: type, value: type } });
}
},
methods: {
...mapActions("api_monsters", ["fetch_monsters"]),
- cr(val) {
+ cr_label(val) {
return (val == 0.125) ? "1/8" :
(val == 0.25) ? "1/4" :
(val == 0.5) ? "1/2" :
val;
},
- selectCR(cr) {
- if(!this.challenge_rating || !this.challenge_rating.includes(cr)) {
- this.challenge_rating.push(cr);
+ setFilter() {
+ this.filter_dialog = false;
+ // Set CR filter
+ if(this.cr.min > 0 || this.cr.max < 30) {
+ const cr = _.range(this.cr.min, this.cr.max+1);
+ if(this.cr.min === 0) {
+ cr.unshift([0.125, 0.25, 0.5]);
+ }
+ this.$set(this.filter, "cr", cr);
} else {
- this.challenge_rating = this.challenge_rating.filter(item => item !== cr);
+ this.$delete(this.filter, "cr");
}
+
+ // Set type filter
+ if(!this.types || !this.types.length || this.types.length === this.monster_types.length) {
+ this.$delete(this.filter, "types");
+ } else {
+ this.$set(this.filter, "types", this.types);
+ }
+ this.filterMonsters();
+ },
+ clearFilter() {
+ this.filter_dialog = false;
+ this.$set(this, "filter", {});
+ this.filterMonsters();
},
- filter() {
+ filterMonsters() {
this.loading = true;
this.monsters = [];
this.pagination.page = 1;
this.query = {
search: this.search,
- types: this.types,
- challenge_ratings: this.challenge_rating
+ types: this.filter.types,
+ challenge_ratings: this.filter.cr
}
this.fetchMonsters();
},
diff --git a/src/views/Compendium/Spells.vue b/src/views/Compendium/Spells.vue
index 2800e7e31..2219a163c 100644
--- a/src/views/Compendium/Spells.vue
+++ b/src/views/Compendium/Spells.vue
@@ -3,22 +3,47 @@
-
-
- Filter
+ placeholder="Search"
+ >
+
+
+
+
+ Filter
+
+
+
-
+
+ Nothing found
+ for "{{ query.search }}"
+ with a type of {{ schools.join(" or ") }}
+
-
+
-
+
{{ col.label }}
@@ -52,15 +73,15 @@
-
-
+
+
-
+
@@ -79,83 +100,188 @@
+
+
+
+
+
+
+
+ Level
+
+
+
+
+
\ No newline at end of file
+ },
+ async mounted() {
+ await this.fetchSpells();
+ },
+};
+
diff --git a/src/views/Contribute/Spells.vue b/src/views/Contribute/Spells.vue
index 043c9b7b1..a213c4387 100644
--- a/src/views/Contribute/Spells.vue
+++ b/src/views/Contribute/Spells.vue
@@ -1,10 +1,29 @@
- Contribute to Spells
+ Contribute to Spells
-
-
+
+
+ {{ Object.keys(allFinishedSpells).length }} / {{ Object.keys(allSpells).length }} ({{
+ Math.floor(
+ (Object.keys(allFinishedSpells).length / Object.keys(allSpells).length) * 100
+ )
+ }}%)
+
+
+
+
+
+
-
-
+
+
+
-
- {{ data.item }}
+
+
+ {{ data.item ? data.item.capitalizeEach() : data.item }}
+
-
-
-
-
-
-
-
-
- {{ data.item }}
-
- {{ getPlayerName(data.row.metadata.tagged) }}
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ Approved spells
+
+
+ {{ data.item.capitalizeEach() }}
+
+
@@ -204,153 +260,201 @@
\ No newline at end of file
+}
+
diff --git a/src/views/Contribute/index.vue b/src/views/Contribute/index.vue
index f1ff135e4..050b277e2 100644
--- a/src/views/Contribute/index.vue
+++ b/src/views/Contribute/index.vue
@@ -1,44 +1,34 @@
-
-
-
-
-
-
- {{ item.name }}
-
-
-
-
-
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
\ No newline at end of file
+
diff --git a/src/views/Pages/Cookies.vue b/src/views/Pages/Cookies.vue
new file mode 100644
index 000000000..fd455179f
--- /dev/null
+++ b/src/views/Pages/Cookies.vue
@@ -0,0 +1,42 @@
+
+
+
+
+ Browser cookies, are small text files that are stored on a user's device when they visit a
+ website. Cookies are used to store preferences and provide information to the website's
+ owner.
+
+
Harmless Key's Cookies
+
+
+
+ Name
+ Provider
+ Description
+
+
+
+
+ _ga
+ harmlesskey.com
+ Used to distinguish users for Google Analytics
+
+
+ _ga_#
+ harmlesskey.com
+ Used to persist session state for Google Analytics
+
+
+
+
No third party cookies are stored on Harmless Key
+
+
+
+
+
+
+
diff --git a/src/views/Pages/Error404.vue b/src/views/Pages/Error404.vue
index 4df22c02b..4c1aa2e5b 100644
--- a/src/views/Pages/Error404.vue
+++ b/src/views/Pages/Error404.vue
@@ -48,7 +48,6 @@
Compendium
Feedback
- Planned
Changelog
Documentation
About us
diff --git a/src/views/Pages/Sitemap.vue b/src/views/Pages/Sitemap.vue
index cdee9e943..ecb0ae14e 100644
--- a/src/views/Pages/Sitemap.vue
+++ b/src/views/Pages/Sitemap.vue
@@ -11,7 +11,6 @@
Documentation
Feedback
Changelog
- Planned
Privacy Policy
Compendium
@@ -44,6 +43,7 @@
+ Spell Creator
Character Builder
diff --git a/src/views/Tools/CombatTracker.vue b/src/views/Tools/CombatTracker.vue
index 87f80fb36..6f012d667 100644
--- a/src/views/Tools/CombatTracker.vue
+++ b/src/views/Tools/CombatTracker.vue
@@ -1,82 +1,92 @@
-
+
+
+ Try Demo Encounter
+
-
- Try Demo Encounter
+
+ Harmless Key is probably the most advanced combat tracker for Dungeons & Dragons. Designed
+ specifically for in-person play, our tools can be of some use to almost any dungeon master.
+
+
+ Of course basic stats basic stats, like initiative and hit points are tracked, but one of the
+ key features that sets Harmless Key apart, is the tracking of important details like bonuses
+ and setting reminders. With just a few clicks, you can add or remove combatants, track damage,
+ and manage other aspects of the battle. This saves time and ensures that combat encounters run
+ smoothly and efficiently and you have time to focus more on what's really important in your
+ games.
+
+
+ Overall, Harmless Key is a versatile tool that can enhance your Dungeons & Dragons gameplay
+ experience in numerous ways. Whether you're a dungeon master looking to streamline combat
+ encounters and focus on the action and adventure of your game, or a player who wants to stay
+ on top of the battle and make informed decisions, Harmless Key has the features you need to
+ take your game to the next level.
+
-
+ Features
+
-
- Our initiative Tracker is probably the most advanced D&D Combat Tracker you can find online.
- During combat it tracks almost anything you can think of, making running encounters so much easier and more fun to do.
-
+
+
+
+
+
+
+
+
+
+
+
- Features
-
+
+ These are some of our features, but our combat tracker has a lot more to offer.
+ Test our demo encounter to see for yourself what the
+ possibilities are.
+
-
-
-
-
-
-
-
-
-
-
-
+ Share the initiative list
+
-
- These are some of our features, but our combat tracker has a lot more to offer.
- Test our demo encounter to see for yourself what the possibilities are.
-
+
+
+
+
+
+
+
+
+
+
+
- Share the initiative list
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- To fully use our Combat Tracker you need an account, but you can try out all it has to offer in our demo encounter.
-
+ To fully use our Combat Tracker you need an account, but you can try out all it has to offer
+ in our demo encounter.
+
diff --git a/src/views/Tools/EncounterBuilder.vue b/src/views/Tools/EncounterBuilder.vue
index a14f87766..82ff1daba 100644
--- a/src/views/Tools/EncounterBuilder.vue
+++ b/src/views/Tools/EncounterBuilder.vue
@@ -1,93 +1,103 @@
-
+
+
+
+ Create encounter
+
+
-
-
- Create encounter
-
-
+
+ Encounter Builder
+
+ Our Dungeons & Dragons encounter builder is a tool designed to help dungeon masters create
+ exciting and challenging combat encounters for their players and run them in our
+ Combat Tracker . With our easy-to-use
+ interface, you can quickly and easily build encounters using a variety of monsters,
+ atmospheric features, and other elements.
+
+
+ The encounter builder allows you to add monsters with either rolled or average hit points,
+ depending on your preference. This gives you greater flexibility and control over your
+ encounters.
+
-
- Encounter Builder
-
- Easily add monsters and players to create an encounter that you can run in our Combat Tracker.
-
-
- Use the average monster HP or roll HP based on hit dice.
-
+
+
-
-
+
+
+
Difficulty
+
+ One of the features of our encounter builder is the built-in difficulty calculator. This
+ tool uses the official rules to analyze your encounter and calculate its difficulty level
+ based on your party's size, level, and other factors. This makes it easy to ensure that
+ your encounters are appropriately challenging for your players, without overwhelming them
+ or making combat encounters too easy.
+
+
+
+
-
-
-
Difficulty
-
Encounter difficulty is calculated as you add or remove monsters to your encounter
-
-
-
-
- To save encounters, you need an account.
-
-
+ To save encounters, you need an account.
+
diff --git a/src/views/Tools/MonsterCreator.vue b/src/views/Tools/MonsterCreator.vue
index e4d87f3a9..07dd3f106 100644
--- a/src/views/Tools/MonsterCreator.vue
+++ b/src/views/Tools/MonsterCreator.vue
@@ -1,168 +1,182 @@
-
+
+
+ Create custom monster
+
-
- Create custom monster
-
+
+ With our monster creator you can create stat blocks for custom monsters that you can use in
+ our combat tracker .
+
-
- With our monster creator you can create stat blocks for custom monsters that you can use in our combat tracker .
-
+
+ Actions
+
+ Advanced actions that can be rolled with one click can easily be created for a monster.
+ Different types of damage can be added as separate rolls and this allows us to apply
+ different damage types separately.
+
+
+ If you click the button below, you roll the action from image. The example roll is a
+ versatile action, which means it can be rolled in 2 ways. In this case you can either
+ perform the attack 1-handed or 2-handed. The damage rolls will be slightly different for
+ both options.
+ Since this example is a weapon attack, it can be rolled with
+ Advantage or Disadvantage . When
+ clicking on the option you want to roll, hold [shift] to roll with advantage or hold [ctrl]
+ to roll with disadvantage.
+
-
- Actions
-
- Advanced actions that can be rolled with one click can easily be created for a monster.
- Different types of damage can be added as separate rolls and this allows us to apply different damage types separately.
-
-
- If you click the button below, you roll the action from image.
- The example roll is a versatile action, which means it can be rolled in 2 ways. In this case you can either perform the attack 1 handed or 2 handed.
- The damage rolls will be slightly different for both options.
- Since this example is a weapon attack, it can be rolled with Advantage or Disadvantage .
- When clicking on the option you want to roll, hold [shift] to roll with advantage or hold [ctrl] to roll with disadvantage.
-
+
+ It is very simple to create actions like these yourself, just fill in a few form fields in
+ the monster creator.
+
- It is very simple to create actions like these yourself, just fill in a few form fields in the monster creator.
+
+
+
+
+
+ Roll example action
+
+
+
-
-
-
-
- Roll example action
-
-
-
-
- Burning Spear
-
-
-
-
-
- {{ i + 1 }}
-
- {{ action.action_list[0][`versatile_${i ? 'two' : 'one'}`]}}
-
-
-
-
-
-
-
-
+
+
+
Resistances and Vulnerabilities
+
+ You can define what types of damage the monster is vulnerable ,
+ resistant or immune to.
+ When you apply damage to the monster in our combat tracker, the amount is automatically
+ adjusted based on these resistances. When you apply 5 cold damage to a monster that is
+ vulnerable to this damage type, the damage is doubled, resulting in 10 frost damage.
+
+
+
+
-
-
-
Resistances and Vulnerabilities
-
- You can define what types of damage the monster is vulnerable , resistant or immune to.
- When you apply damage to the monster in our combat tracker, the amount is automatically adjusted based on these resistances.
- When you apply 5 cold damage to a monster that is vulnerable to this damage type, the damage is doubled, resulting in 10 frost damage.
-
-
-
-
+
+ Spellcasters
+
+ In combination with our
+ spell creator you can create powerful
+ spellcasters that are easy to use in our combat tracker. Unleashing magic
+ users on your party can be a pain for you as a DM, but with our combined tools that is no
+ longer the case. Spell slots are tracked for you and casting a spell is as simple as using
+ any other monster action in Harmless Key.
+
+
- To save a monster, you need an account, but you can always download your creations.
-
+
+ Share your creations
+
+ Our monster creator also makes it easy to share your custom monsters with other dungeon
+ masters. You can save your monsters to your library if you have an account, export them to
+ share with others, or import monsters created by other Harmless Key users.
+ If you're a content creator you can provide your followers with monsters that they can
+ directly use in Harmless Key.
+
+
+ To save a monster, you need an account, but you can always download your creations.
+
diff --git a/src/views/Tools/SpellCreator.vue b/src/views/Tools/SpellCreator.vue
new file mode 100644
index 000000000..b71de9397
--- /dev/null
+++ b/src/views/Tools/SpellCreator.vue
@@ -0,0 +1,145 @@
+
+
+
+
+ Create custom spell
+
+
+
+
+
+ Our spell creator lets you create custom spells with actions that can be rolled with one
+ click.
+
+
+
+
+ Scaling
+
+ The actions of your spell change depending on caster level, or at what level the spell is
+ casted. Rolls will automatically scale when you cast the spell with a higher lever character, or using a higher spell slot.
+
+
+
+ Cast Blight
+
+
+
+ Cast at level {{ cast_level }}
+
+
+
+
+
+
+ Spellcasting monsters
+
+ You can combine your custom spells with the monsters you've made in Harmless Key to create
+ powerful spellcasters that are easy to use in our
+ combat tracker .
+
+
+
+
+ Share your creations
+
+ Our spell creator also makes it easy to share your custom spells with other dungeon masters
+ and players. You can save your spells to your library if you have an account, export them to
+ share with others, or import spells created by other Harmless Key users.
+ If you're a content creator you can provide your followers with spells that they can
+ directly use in Harmless Key.
+
+
+
+
+
+
+
+
+.caster__levels {
+ display: flex;
+ justify-content: center;
+ column-gap: 3px;
+ margin-bottom: 10px;
+
+ .level {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ text-align: center;
+ cursor: pointer;
+ background-color: $neutral-8;
+ user-select: none;
+
+ &.selected {
+ background-color: $blue;
+ color: $neutral-1;
+ }
+ &.disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/src/views/Tools/index.vue b/src/views/Tools/index.vue
index 4ef66851d..89a473b6e 100644
--- a/src/views/Tools/index.vue
+++ b/src/views/Tools/index.vue
@@ -4,123 +4,144 @@
D&D 5e Tools
-
-
-
-
-
-
-
-
-
-
- {{ tool.description }}
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {{ tool.description }}
+
+
+
+
+
+
diff --git a/src/views/UserContent/Encounters/Edit/Entities.vue b/src/views/UserContent/Encounters/Edit/Entities.vue
index c512ccd4a..5dc0f4573 100644
--- a/src/views/UserContent/Encounters/Edit/Entities.vue
+++ b/src/views/UserContent/Encounters/Edit/Entities.vue
@@ -1,713 +1,785 @@
-
-
-
- Players
-
-
-
-
-
-
-
-
+
+
+
+ Players
+
+
+
+
+
+
+
+
+
+
+ Add {{ player.character_name }}
+
-
- Add {{ player.character_name }}
-
-
-
-
No Players Yet
-
Add players to your campaign first.
-
Go to campaign
+
+
No Players Yet
+
Add players to your campaign first.
+
Go to campaign
+
+
Add all
- Add all
-
-
Missing players? Add them to your campaign first .
-
-
-
-
- Add players
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ props.value }}
-
-
- {{ props.value }}
-
-
-
-
-
-
-
-
-
-
-
-
+ Missing players?
+ Add them to your campaign first .
+
+
+
+
+
+ Add players
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
- {{ col.value }}
+
+ {{ props.value }}
-
- {{ col.value }}
+
+ {{ props.value }}
+
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+ {{ col.value }}
+
+
+
{{ col.value }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
+
diff --git a/src/views/UserContent/ManageContent.vue b/src/views/UserContent/ManageContent.vue
index 72f3813e1..c26d5f427 100644
--- a/src/views/UserContent/ManageContent.vue
+++ b/src/views/UserContent/ManageContent.vue
@@ -3,10 +3,12 @@
You're over encumbered
- You can either delete some of your content, or get a subscription that allows for more.
+
+ You can either delete some of your content, or get a subscription that allows for more.
+
-
- {{ tier.name === "Free" ? "Get" : "Upgrade" }} supscription
+
+ {{ tier.name === "Free" ? "Get" : "Upgrade" }} subscription
@@ -17,47 +19,46 @@
\ No newline at end of file
+export default {
+ name: "manage-content",
+ components: {
+ Content,
+ },
+ data() {
+ return {
+ content_types: [
+ {
+ type: "campaigns",
+ icon: "fa-dungeon",
+ },
+ {
+ type: "players",
+ icon: "fa-user",
+ },
+ {
+ type: "npcs",
+ icon: "fa-dragon",
+ },
+ {
+ type: "spells",
+ icon: "fa-wand-magic",
+ },
+ {
+ type: "reminders",
+ icon: "fa-stopwatch",
+ },
+ {
+ type: "items",
+ icon: "fa-staff",
+ },
+ ],
+ };
+ },
+ computed: {
+ ...mapGetters(["user", "tier", "content_count", "overencumbered"]),
+ },
+};
+
diff --git a/src/views/UserContent/Npcs/EditNpc.vue b/src/views/UserContent/Npcs/EditNpc.vue
index 61283163d..631ba1205 100644
--- a/src/views/UserContent/Npcs/EditNpc.vue
+++ b/src/views/UserContent/Npcs/EditNpc.vue
@@ -1,6 +1,6 @@
-
+
@@ -39,7 +39,11 @@
There are validation errors
-
Cancel
+
{{ unsaved_changes ? "Cancel" : "Back" }}
Download
@@ -49,8 +53,8 @@
Unsaved changes
-
-
+
+
{{ userId ? "Revert" : "Reset" }}
@@ -80,168 +84,186 @@
\ No newline at end of file
+
diff --git a/src/views/UserContent/Npcs/Npcs.vue b/src/views/UserContent/Npcs/Npcs.vue
index 6cdd8c60b..3fa840d82 100644
--- a/src/views/UserContent/Npcs/Npcs.vue
+++ b/src/views/UserContent/Npcs/Npcs.vue
@@ -2,25 +2,28 @@
-
+
Export NPCs
-
-
+
+
Import NPCs
-
+
-
+
-
- These are your custom Non-Player Characters and monsters.
-
+
These are your custom Non-Player Characters and monsters.
-
@@ -40,19 +43,22 @@
:pagination="{ rowsPerPage: 15 }"
:filter="search"
wrap-cells
- >
+ >
-
-
+
-
+
{{ props.value }}
@@ -64,21 +70,18 @@
-
- Edit
-
+ Edit
-
- Download
-
+ Download
-
+
-
- Delete
-
+ Delete
@@ -87,10 +90,18 @@
-
+
Create your first NPC
-
+
Get more NPC slots
@@ -103,10 +114,10 @@
-
+
@@ -114,144 +125,139 @@
\ No newline at end of file
+
+ const json_export = Object.values(all_npcs);
+ downloadJSON(json_export);
+ },
+ async exportNPC(id) {
+ const npc = await this.get_npc({ uid: this.userId, id });
+ npc.harmless_key = id;
+ downloadJSON(npc);
+ },
+ },
+};
+
diff --git a/src/views/UserContent/Players/Defenses.vue b/src/views/UserContent/Players/Defenses.vue
deleted file mode 100644
index dac5fc99d..000000000
--- a/src/views/UserContent/Players/Defenses.vue
+++ /dev/null
@@ -1,176 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {{ type === "damage_vulnerabilities" ? "V" : type === "damage_resistances" ? "R" : "I" }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/views/UserContent/Players/EditPlayer.vue b/src/views/UserContent/Players/EditPlayer.vue
index b33671910..dccf69bc6 100644
--- a/src/views/UserContent/Players/EditPlayer.vue
+++ b/src/views/UserContent/Players/EditPlayer.vue
@@ -85,7 +85,6 @@
- No update
+ {{ playerEqualsLinkedCharacter() ? "Update" : "No update" }}
@@ -689,7 +688,7 @@ import GiveCharacterControl from "./GiveCharacterControl.vue";
import { mapGetters, mapActions } from "vuex";
import { experience } from "src/mixins/experience.js";
import { general } from "src/mixins/general.js";
-import Defenses from "./Defenses";
+import Defenses from "src/components/npcs/Defenses";
import CopyContent from "../../../components/CopyContent.vue";
import { abilities, skills } from "src/utils/generalConstants";
import {
@@ -862,6 +861,7 @@ export default {
}
},
savePlayer() {
+ console.log(this.player);
if (this.$route.name === "Add player") {
this.addPlayer();
} else {
diff --git a/src/views/UserContent/Spells/EditSpell.vue b/src/views/UserContent/Spells/EditSpell.vue
new file mode 100644
index 000000000..d42d8e43d
--- /dev/null
+++ b/src/views/UserContent/Spells/EditSpell.vue
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+ There are validation errors
+
+
+
+
+ Copy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ There are validation errors
+
+
+ {{ unsaved_changes ? "Cancel" : "Back" }}
+
+
+ Download
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/UserContent/Spells/Spells.vue b/src/views/UserContent/Spells/Spells.vue
new file mode 100644
index 000000000..b1625e583
--- /dev/null
+++ b/src/views/UserContent/Spells/Spells.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+ Import spells
+
+
+
+
+
These are your custom spells.
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.value }}
+
+
+ {{ props.value }}
+
+
+
+
+
+
+
+ Edit
+
+
+
+ Download
+
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+ Create your first Spell
+
+
+ Get more spell slots
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/UserContent/index.vue b/src/views/UserContent/index.vue
index fdc4d37cb..d12115e8b 100644
--- a/src/views/UserContent/index.vue
+++ b/src/views/UserContent/index.vue
@@ -4,19 +4,19 @@
-
-
+ >
Continue
- {{ active_campaign.name }}
+ {{ active_campaign.name }}
Dive right back into your adventure.
@@ -29,15 +29,11 @@
Campaigns
-
- No active campaign
-
+
No active campaign
Start your first adventure.
-
- Campaign overview
-
+
Campaign overview
@@ -45,9 +41,10 @@
-
@@ -65,13 +62,14 @@
-
+
-
@@ -82,7 +80,9 @@
{{ label }}
{{ caption }}
- {{ badge }}
+ {{ badge }}
@@ -95,188 +95,191 @@
\ No newline at end of file
+}
+
diff --git a/src/views/profile/Profile.vue b/src/views/profile/Profile.vue
index bdac14fea..46010340c 100644
--- a/src/views/profile/Profile.vue
+++ b/src/views/profile/Profile.vue
@@ -3,92 +3,119 @@
-
-
-
-
-
-
- Need more slots?
-
-
-
-
+
-
Patreon: {{ userInfo.patron.tier }}
-
Thank you so much for your support.
+
+ Patreon:
+ {{ userInfo.patron.tier }}
+
+
+ Thank you so much for your support.
+
+
-
+
Payment Declined
- Your last payment on Patreon was declined, your subscription will automatically be cancelled on {{ makeDate(userInfo.patron.pledge_end) }} .
- Go to patreon.com to check your payment details.
+ Your last payment on Patreon was declined, your subscription will automatically
+ be cancelled on {{ makeDate(userInfo.patron.pledge_end) }} .
+ Go to
+ patreon.com
+ to check your payment details.
Your subscription expired
-
+
Renew
-
Cancel subscription
-
+
Cancel subscription
+
-
-
Subscription tier: {{ tier.name }}
+
+ Subscription tier: {{ tier.name }}
+
You have unlimited power.
-
+
Support us for more slots
-
-
Voucher subscription
-
- {{ voucher.message }}
- Your voucher ends on:
- {{ makeDate(voucher.date, false) }}
- never .
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
Voucher subscription
+
+ {{ voucher.message }}
+
+ Your voucher ends on:
+ {{ makeDate(voucher.date, false) }}
+ never .
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
+
+
-
+
+
+ Need more slots?
+
@@ -97,8 +124,12 @@
-
{{ resetError }}
-
{{ resetSuccess }}
+
+ {{ resetError }}
+
+
+ {{ resetSuccess }}
+