Added basic XP system

This commit is contained in:
Stefan Zermatten
2020-06-05 16:14:26 +02:00
parent efb8b87a2d
commit d1e7eb2fa0
14 changed files with 589 additions and 22 deletions

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View File

@@ -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,