Added basic XP system
This commit is contained in:
@@ -65,16 +65,21 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mechanics
|
// Mechanics
|
||||||
deathSave: {
|
deathSave: {
|
||||||
type: deathSaveSchema,
|
type: deathSaveSchema,
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
},
|
},
|
||||||
|
// Sum of all XP gained by this character
|
||||||
xp: {
|
xp: {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
|
// Sum of all levels granted by milestone XP
|
||||||
|
xpLevels: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
defaultValue: 0,
|
||||||
|
},
|
||||||
weightCarried: {
|
weightCarried: {
|
||||||
type: Number,
|
type: Number,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
|
|||||||
164
app/imports/api/creature/experience/Experiences.js
Normal file
164
app/imports/api/creature/experience/Experiences.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
|
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
|
||||||
|
let Experiences = new Mongo.Collection('experiences');
|
||||||
|
|
||||||
|
let ExperienceSchema = new SimpleSchema({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// The amount of XP this experience gives
|
||||||
|
xp: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
// Setting levels instead of value grants whole levels
|
||||||
|
levels: {
|
||||||
|
type: SimpleSchema.Integer,
|
||||||
|
optional: true,
|
||||||
|
min: 0,
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
// The real-world date that it occured, usually sorted by date
|
||||||
|
date: {
|
||||||
|
type: Date,
|
||||||
|
autoValue: function() {
|
||||||
|
// If the date isn't set, set it to now
|
||||||
|
if (!this.isSet) {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Experiences.attachSchema(ExperienceSchema);
|
||||||
|
|
||||||
|
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
||||||
|
assertEditPermission(creatureId, userId);
|
||||||
|
if (experience.xp){
|
||||||
|
Creatures.update(creatureId, {$inc: {xp: experience.xp}});
|
||||||
|
}
|
||||||
|
if (experience.levels) {
|
||||||
|
Creatures.update(creatureId, {$inc: {xpLevels: experience.levels}});
|
||||||
|
}
|
||||||
|
experience.creatureId = creatureId;
|
||||||
|
return Experiences.insert(experience);
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertExperience = new ValidatedMethod({
|
||||||
|
name: 'Experiences.methods.insert',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
experience: {
|
||||||
|
type: ExperienceSchema.omit('creatureId'),
|
||||||
|
},
|
||||||
|
creatureIds: {
|
||||||
|
type: Array,
|
||||||
|
max: 12,
|
||||||
|
},
|
||||||
|
'creatureIds.$': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
run({experience, creatureIds}) {
|
||||||
|
let userId = this.userId;
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('Experiences.methods.insert.denied',
|
||||||
|
'You need to be logged in to insert an experience');
|
||||||
|
}
|
||||||
|
let tier = getUserTier(this.userId);
|
||||||
|
if (!tier.paidBenefits){
|
||||||
|
throw new Meteor.Error('Experiences.methods.insert.denied',
|
||||||
|
`The ${tier.name} tier does not allow you to grant experience`);
|
||||||
|
}
|
||||||
|
let insertedIds = [];
|
||||||
|
creatureIds.forEach(creatureId => {
|
||||||
|
let id = insertExperienceForCreature({experience, creatureId, userId});
|
||||||
|
insertedIds.push(id);
|
||||||
|
});
|
||||||
|
return insertedIds;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeExperience = new ValidatedMethod({
|
||||||
|
name: 'Experiences.methods.remove',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
experienceId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
run({experienceId}) {
|
||||||
|
let userId = this.userId;
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('Experiences.methods.remove.denied',
|
||||||
|
'You need to be logged in to remove an experience');
|
||||||
|
}
|
||||||
|
let tier = getUserTier(this.userId);
|
||||||
|
if (!tier.paidBenefits){
|
||||||
|
throw new Meteor.Error('Experiences.methods.remove.denied',
|
||||||
|
`The ${tier.name} tier does not allow you to remove an experience`);
|
||||||
|
}
|
||||||
|
let experience = Experiences.findOne(experienceId);
|
||||||
|
if (!experience) return;
|
||||||
|
let creatureId = experience.creatureId
|
||||||
|
assertEditPermission(creatureId, userId);
|
||||||
|
if (experience.xp){
|
||||||
|
Creatures.update(creatureId, {$inc: {xp: -experience.xp}});
|
||||||
|
}
|
||||||
|
if (experience.levels) {
|
||||||
|
Creatures.update(creatureId, {$inc: {xpLevels: -experience.levels}});
|
||||||
|
}
|
||||||
|
experience.creatureId = creatureId;
|
||||||
|
return Experiences.remove(experienceId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const recomputeExperiences = new ValidatedMethod({
|
||||||
|
name: 'Experiences.methods.recompute',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
run({creatureId}) {
|
||||||
|
let userId = this.userId;
|
||||||
|
if (!userId) {
|
||||||
|
throw new Meteor.Error('Experiences.methods.recompute.denied',
|
||||||
|
'You need to be logged in to recompute a creature\'s experiences');
|
||||||
|
}
|
||||||
|
let tier = getUserTier(this.userId);
|
||||||
|
if (!tier.paidBenefits){
|
||||||
|
throw new Meteor.Error('Experiences.methods.recompute.denied',
|
||||||
|
`The ${tier.name} tier does not allow you to recompute a creature's experiences`);
|
||||||
|
}
|
||||||
|
assertEditPermission(creatureId, userId);
|
||||||
|
|
||||||
|
let xp = 0;
|
||||||
|
let xpLevels = 0;
|
||||||
|
Experiences.find({
|
||||||
|
creatureId
|
||||||
|
}, {
|
||||||
|
fields: {xp: 1, levels: 1}
|
||||||
|
}).forEach(experience => {
|
||||||
|
xp += experience.xp || 0;
|
||||||
|
xpLevels += experience.levels || 0;
|
||||||
|
});
|
||||||
|
Creatures.update(creatureId, {$set: {xp, xpLevels}});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Experiences;
|
||||||
|
export { ExperienceSchema, insertExperience, removeExperience, recomputeExperiences };
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
|
||||||
let ExperienceSchema = new SimpleSchema({
|
let ExperienceSchema = new SimpleSchema({
|
||||||
name: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
@@ -10,11 +10,6 @@ let ExperienceSchema = new SimpleSchema({
|
|||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
// The amount of XP this experience gives
|
|
||||||
value: {
|
|
||||||
type: SimpleSchema.Integer,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
// The real-world date that it occured
|
// The real-world date that it occured
|
||||||
date: {
|
date: {
|
||||||
type: Date,
|
type: Date,
|
||||||
@@ -30,6 +25,20 @@ let ExperienceSchema = new SimpleSchema({
|
|||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Tags to better find this entry later
|
||||||
|
tags: {
|
||||||
|
type: Array,
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
'tags.$': {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
// ID of the journal this entry belongs to
|
||||||
|
journalId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
index: 1,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export { ExperienceSchema };
|
export { ExperienceSchema };
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js'
|
import CreatureProperties from '/imports/api/creature/CreatureProperties.js'
|
||||||
import { assertOwnership } from '/imports/api/creature/creaturePermissions.js';
|
import { assertOwnership } from '/imports/api/creature/creaturePermissions.js';
|
||||||
|
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
|
||||||
function removeRelatedDocuments(charId){
|
function removeRelatedDocuments(creatureId){
|
||||||
CreatureProperties.remove({'ancestors.id': charId});
|
CreatureProperties.remove({'ancestors.id': creatureId});
|
||||||
};
|
Experiences.remove({creatureId});
|
||||||
|
}
|
||||||
|
|
||||||
const removeCreature = new ValidatedMethod({
|
const removeCreature = new ValidatedMethod({
|
||||||
name: "Creatures.methods.removeCreature", // DDP method name
|
name: 'Creatures.methods.removeCreature', // DDP method name
|
||||||
validate: new SimpleSchema({
|
validate: new SimpleSchema({
|
||||||
charId: {
|
charId: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import { ContainerSchema } from '/imports/api/properties/Containers.js';
|
|||||||
import { DamageSchema } from '/imports/api/properties/Damages.js';
|
import { DamageSchema } from '/imports/api/properties/Damages.js';
|
||||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||||
import { ComputedEffectSchema } from '/imports/api/properties/Effects.js';
|
import { ComputedEffectSchema } from '/imports/api/properties/Effects.js';
|
||||||
import { ExperienceSchema } from '/imports/api/properties/Experiences.js';
|
|
||||||
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { ItemSchema } from '/imports/api/properties/Items.js';
|
import { ItemSchema } from '/imports/api/properties/Items.js';
|
||||||
@@ -33,7 +32,6 @@ const propertySchemasIndex = {
|
|||||||
damage: DamageSchema,
|
damage: DamageSchema,
|
||||||
damageMultiplier: DamageMultiplierSchema,
|
damageMultiplier: DamageMultiplierSchema,
|
||||||
effect: ComputedEffectSchema,
|
effect: ComputedEffectSchema,
|
||||||
experience: ExperienceSchema,
|
|
||||||
feature: FeatureSchema,
|
feature: FeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: NoteSchema,
|
note: NoteSchema,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
|||||||
import { DamageSchema } from '/imports/api/properties/Damages.js';
|
import { DamageSchema } from '/imports/api/properties/Damages.js';
|
||||||
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
import { DamageMultiplierSchema } from '/imports/api/properties/DamageMultipliers.js';
|
||||||
import { EffectSchema } from '/imports/api/properties/Effects.js';
|
import { EffectSchema } from '/imports/api/properties/Effects.js';
|
||||||
import { ExperienceSchema } from '/imports/api/properties/Experiences.js';
|
|
||||||
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
||||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||||
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
||||||
@@ -33,7 +32,6 @@ const propertySchemasIndex = {
|
|||||||
damage: DamageSchema,
|
damage: DamageSchema,
|
||||||
damageMultiplier: DamageMultiplierSchema,
|
damageMultiplier: DamageMultiplierSchema,
|
||||||
effect: EffectSchema,
|
effect: EffectSchema,
|
||||||
experience: ExperienceSchema,
|
|
||||||
feature: FeatureSchema,
|
feature: FeatureSchema,
|
||||||
folder: FolderSchema,
|
folder: FolderSchema,
|
||||||
note: NoteSchema,
|
note: NoteSchema,
|
||||||
|
|||||||
@@ -39,10 +39,6 @@ const PROPERTIES = Object.freeze({
|
|||||||
icon: '$vuetify.icons.effect',
|
icon: '$vuetify.icons.effect',
|
||||||
name: 'Effect'
|
name: 'Effect'
|
||||||
},
|
},
|
||||||
experience: {
|
|
||||||
icon: '$vuetify.icons.experience',
|
|
||||||
name: 'Experience'
|
|
||||||
},
|
|
||||||
feature: {
|
feature: {
|
||||||
icon: 'subject',
|
icon: 'subject',
|
||||||
name: 'Feature'
|
name: 'Feature'
|
||||||
|
|||||||
32
app/imports/server/publications/experiences.js
Normal file
32
app/imports/server/publications/experiences.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
|
||||||
|
let schema = new SimpleSchema({
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Meteor.publish('experiences', function(creatureId){
|
||||||
|
schema.validate({ creatureId });
|
||||||
|
this.autorun(function (){
|
||||||
|
let userId = this.userId;
|
||||||
|
let creatureCursor = Creatures.find({
|
||||||
|
_id: creatureId,
|
||||||
|
$or: [
|
||||||
|
{readers: userId},
|
||||||
|
{writers: userId},
|
||||||
|
{owner: userId},
|
||||||
|
{public: true},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
if (!creatureCursor.count()) return this.ready();
|
||||||
|
return [
|
||||||
|
Experiences.find({
|
||||||
|
creatureId,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import '/imports/server/publications/characterList.js';
|
import '/imports/server/publications/characterList.js';
|
||||||
import '/imports/server/publications/library.js';
|
import '/imports/server/publications/library.js';
|
||||||
import '/imports/server/publications/singleCharacter.js';
|
import '/imports/server/publications/singleCharacter.js';
|
||||||
|
import '/imports/server/publications/experiences.js';
|
||||||
import '/imports/server/publications/users.js';
|
import '/imports/server/publications/users.js';
|
||||||
import '/imports/server/publications/icons.js';
|
import '/imports/server/publications/icons.js';
|
||||||
|
|||||||
@@ -20,6 +20,61 @@
|
|||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<v-card class="class-details">
|
||||||
|
<v-card-title
|
||||||
|
v-if="creature.variables.level"
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
Level {{ creature.variables.level.value }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-list v-if="highestClassLevels.length">
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title v-if="creature.xpLevels">
|
||||||
|
{{ creature.xpLevels }} Levels gained
|
||||||
|
</v-list-tile-title>
|
||||||
|
<v-list-tile-title v-else>
|
||||||
|
{{ creature.xp }} XP
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
icon
|
||||||
|
data-id="experience-info-button"
|
||||||
|
@click="showExperienceList"
|
||||||
|
>
|
||||||
|
<v-icon>info</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-tile-action>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
icon
|
||||||
|
data-id="experience-add-button"
|
||||||
|
@click="addExperience"
|
||||||
|
>
|
||||||
|
<v-icon>add</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-list-tile
|
||||||
|
v-for="classLevel in highestClassLevels"
|
||||||
|
:key="classLevel._id"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
{{ classLevel.name }}
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-avatar>
|
||||||
|
{{ classLevel.level }}
|
||||||
|
</v-list-tile-avatar>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
v-for="note in notes"
|
v-for="note in notes"
|
||||||
:key="note._id"
|
:key="note._id"
|
||||||
@@ -37,6 +92,7 @@ import Creatures from '/imports/api/creature/Creatures.js';
|
|||||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||||
|
import getActiveProperties from '/imports/api/creature/getActiveProperties.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -44,7 +100,10 @@ export default {
|
|||||||
NoteCard,
|
NoteCard,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
creatureId: String,
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
notes(){
|
notes(){
|
||||||
@@ -58,8 +117,34 @@ export default {
|
|||||||
},
|
},
|
||||||
creature(){
|
creature(){
|
||||||
return Creatures.findOne(this.creatureId);
|
return Creatures.findOne(this.creatureId);
|
||||||
}
|
},
|
||||||
|
classLevels(){
|
||||||
|
return getActiveProperties({
|
||||||
|
ancestorId: this.creatureId,
|
||||||
|
filter: {type: 'classLevel'},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
highestClassLevels(){
|
||||||
|
let highestLevels = {};
|
||||||
|
let highestLevelsList = [];
|
||||||
|
this.classLevels.forEach(classLevel => {
|
||||||
|
let name = classLevel.vairableName;
|
||||||
|
if (
|
||||||
|
!highestLevels[name] ||
|
||||||
|
highestLevels[name].level < classLevel.level
|
||||||
|
){
|
||||||
|
highestLevels[name] = classLevel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (let name in highestLevels){
|
||||||
|
highestLevelsList.push(highestLevels[name]);
|
||||||
|
}
|
||||||
|
highestLevelsList.sort((a, b) => a.level - b.level);
|
||||||
|
return highestLevelsList;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showCharacterForm(){
|
showCharacterForm(){
|
||||||
this.$store.commit('pushDialogStack', {
|
this.$store.commit('pushDialogStack', {
|
||||||
@@ -70,6 +155,25 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addExperience(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'experience-insert-dialog',
|
||||||
|
elementId: 'experience-add-button',
|
||||||
|
data: {
|
||||||
|
creatureIds: [this.creatureId],
|
||||||
|
startAsMilestone: !!this.creature.xpLevels
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showExperienceList(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'experience-list-dialog',
|
||||||
|
elementId: 'experience-info-button',
|
||||||
|
data: {
|
||||||
|
creatureId: this.creatureId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
66
app/imports/ui/creature/experiences/ExperienceForm.vue
Normal file
66
app/imports/ui/creature/experiences/ExperienceForm.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="experience-form">
|
||||||
|
<div class="layout column align-center">
|
||||||
|
<smart-switch
|
||||||
|
label="Milestone"
|
||||||
|
class="mx-3"
|
||||||
|
:value="milestone"
|
||||||
|
@change="makeMilestone"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
v-if="milestone"
|
||||||
|
label="Levels"
|
||||||
|
type="number"
|
||||||
|
class="base-value-field text-xs-center large-format no-flex"
|
||||||
|
:value="model.levels"
|
||||||
|
:error-messages="errors.levels"
|
||||||
|
@change="change('levels', ...arguments)"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
v-else
|
||||||
|
type="number"
|
||||||
|
class="base-value-field text-xs-center large-format no-flex"
|
||||||
|
suffix="XP"
|
||||||
|
:value="model.xp"
|
||||||
|
:error-messages="errors.xp"
|
||||||
|
@change="change('xp', ...arguments)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<text-field
|
||||||
|
label="Name"
|
||||||
|
:value="model.name"
|
||||||
|
:error-messages="errors.name"
|
||||||
|
@change="change('name', ...arguments)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [propertyFormMixin],
|
||||||
|
props: {
|
||||||
|
startAsMilestone: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){return {
|
||||||
|
milestone: this.startAsMilestone,
|
||||||
|
}},
|
||||||
|
methods: {
|
||||||
|
makeMilestone(milestone, ack){
|
||||||
|
this.milestone = milestone;
|
||||||
|
if (milestone){
|
||||||
|
this.change('xp', undefined);
|
||||||
|
this.change('levels', 1, ack);
|
||||||
|
} else {
|
||||||
|
this.change('levels', undefined, ack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<experience-form
|
||||||
|
:start-as-milestone="startAsMilestone"
|
||||||
|
:model="model"
|
||||||
|
:errors="errors"
|
||||||
|
@change="change"
|
||||||
|
@push="push"
|
||||||
|
@pull="pull"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
v-if="error"
|
||||||
|
class="error"
|
||||||
|
>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
slot="actions"
|
||||||
|
class="layout row justify-end"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
:disabled="!valid"
|
||||||
|
:loading="loading"
|
||||||
|
@click="insertExperience"
|
||||||
|
>
|
||||||
|
Insert
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import ExperienceForm from '/imports/ui/creature/experiences/ExperienceForm.vue';
|
||||||
|
import { ExperienceSchema, insertExperience } from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
import schemaFormMixin from '/imports/ui/properties/forms/shared/schemaFormMixin.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
ExperienceForm,
|
||||||
|
},
|
||||||
|
mixins: [schemaFormMixin],
|
||||||
|
provide: {
|
||||||
|
context: {
|
||||||
|
debounceTime: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureIds: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
startAsMilestone: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
let schema = ExperienceSchema.omit('creatureId');
|
||||||
|
let startingModel = {};
|
||||||
|
if (this.startAsMilestone){
|
||||||
|
startingModel.levels = 1;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
model: schema.clean(startingModel),
|
||||||
|
schema: schema,
|
||||||
|
validationContext: schema.newContext(),
|
||||||
|
debounceTime: 0,
|
||||||
|
loading: false,
|
||||||
|
error: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
insertExperience(){
|
||||||
|
this.loading = true;
|
||||||
|
let experience = this.schema.clean(this.model);
|
||||||
|
insertExperience.call({
|
||||||
|
experience,
|
||||||
|
creatureIds: this.creatureIds,
|
||||||
|
}, (error) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (error){
|
||||||
|
this.error = error.message || error;
|
||||||
|
console.error(error);
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch('popDialogStack');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
92
app/imports/ui/creature/experiences/ExperienceListDialog.vue
Normal file
92
app/imports/ui/creature/experiences/ExperienceListDialog.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<template slot="toolbar">
|
||||||
|
<v-toolbar-title>
|
||||||
|
Experiences
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
flat
|
||||||
|
@click="recompute"
|
||||||
|
>
|
||||||
|
<v-icon>refresh</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-tile
|
||||||
|
v-for="experience in experiences"
|
||||||
|
:key="experience._id"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>
|
||||||
|
{{ experience.name }}
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
flat
|
||||||
|
:loading="experiencesRemovalLoading.has(experience._id)"
|
||||||
|
@click="removeExperience(experience._id)"
|
||||||
|
>
|
||||||
|
<v-icon>delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import Experiences, { removeExperience, recomputeExperiences } from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
|
experiencesRemovalLoading: new Set(),
|
||||||
|
recomputeLoading: false,
|
||||||
|
}},
|
||||||
|
meteor: {
|
||||||
|
$subscribe: {
|
||||||
|
'experiences'(){
|
||||||
|
return [this.creatureId];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
experiences(){
|
||||||
|
return Experiences.find({
|
||||||
|
creatureId: this.creatureId
|
||||||
|
}, {
|
||||||
|
sort: {date: 1}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeExperience(experienceId){
|
||||||
|
this.experiencesRemovalLoading.add(experienceId);
|
||||||
|
removeExperience.call({experienceId}, (error) => {
|
||||||
|
this.experiencesRemovalLoading.delete(experienceId);
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
recompute(){
|
||||||
|
this.recomputeLoading = true;
|
||||||
|
recomputeExperiences.call({creatureId: this.creatureId}, error => {
|
||||||
|
this.recomputeLoading = false;
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -3,6 +3,8 @@ import CreaturePropertyCreationDialog from '/imports/ui/creature/creaturePropert
|
|||||||
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
||||||
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
import CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.vue'
|
||||||
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
import DeleteConfirmationDialog from '/imports/ui/dialogStack/DeleteConfirmationDialog.vue';
|
||||||
|
import ExperienceInsertDialog from '/imports/ui/creature/experiences/ExperienceInsertDialog.vue';
|
||||||
|
import ExperienceListDialog from '/imports/ui/creature/experiences/ExperienceListDialog.vue';
|
||||||
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
import InviteDialog from '/imports/ui/user/InviteDialog.vue';
|
||||||
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
|
import LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.vue';
|
||||||
import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue';
|
import LibraryEditDialog from '/imports/ui/library/LibraryEditDialog.vue';
|
||||||
@@ -13,13 +15,14 @@ import ShareDialog from '/imports/ui/sharing/ShareDialog.vue';
|
|||||||
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
||||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
CreatureFormDialog,
|
CreatureFormDialog,
|
||||||
CreaturePropertyCreationDialog,
|
CreaturePropertyCreationDialog,
|
||||||
CreaturePropertyDialog,
|
CreaturePropertyDialog,
|
||||||
CreaturePropertyFromLibraryDialog,
|
CreaturePropertyFromLibraryDialog,
|
||||||
DeleteConfirmationDialog,
|
DeleteConfirmationDialog,
|
||||||
|
ExperienceInsertDialog,
|
||||||
|
ExperienceListDialog,
|
||||||
InviteDialog,
|
InviteDialog,
|
||||||
LibraryCreationDialog,
|
LibraryCreationDialog,
|
||||||
LibraryEditDialog,
|
LibraryEditDialog,
|
||||||
|
|||||||
Reference in New Issue
Block a user