Added basic XP system
This commit is contained in:
@@ -20,6 +20,61 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</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
|
||||
v-for="note in notes"
|
||||
:key="note._id"
|
||||
@@ -37,6 +92,7 @@ import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import NoteCard from '/imports/ui/properties/components/persona/NoteCard.vue';
|
||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -44,7 +100,10 @@ export default {
|
||||
NoteCard,
|
||||
},
|
||||
props: {
|
||||
creatureId: String,
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
notes(){
|
||||
@@ -58,8 +117,34 @@ export default {
|
||||
},
|
||||
creature(){
|
||||
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: {
|
||||
showCharacterForm(){
|
||||
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>
|
||||
|
||||
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 CreaturePropertyFromLibraryDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyFromLibraryDialog.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 LibraryCreationDialog from '/imports/ui/library/LibraryCreationDialog.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 UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||
|
||||
|
||||
export default {
|
||||
CreatureFormDialog,
|
||||
CreaturePropertyCreationDialog,
|
||||
CreaturePropertyDialog,
|
||||
CreaturePropertyFromLibraryDialog,
|
||||
DeleteConfirmationDialog,
|
||||
ExperienceInsertDialog,
|
||||
ExperienceListDialog,
|
||||
InviteDialog,
|
||||
LibraryCreationDialog,
|
||||
LibraryEditDialog,
|
||||
|
||||
Reference in New Issue
Block a user