Iterated on XP system
This commit is contained in:
@@ -70,24 +70,26 @@ let CreatureSchema = new SimpleSchema({
|
|||||||
type: deathSaveSchema,
|
type: deathSaveSchema,
|
||||||
defaultValue: {},
|
defaultValue: {},
|
||||||
},
|
},
|
||||||
|
// Stats that are computed and denormalised outside of recomputation
|
||||||
|
denormalizedStats: {
|
||||||
|
type: Object,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
// Sum of all XP gained by this character
|
// Sum of all XP gained by this character
|
||||||
xp: {
|
'denormalizedStats.xp': {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
// Sum of all levels granted by milestone XP
|
// Sum of all levels granted by milestone XP
|
||||||
xpLevels: {
|
'denormalizedStats.milestoneLevels': {
|
||||||
type: SimpleSchema.Integer,
|
type: SimpleSchema.Integer,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
weightCarried: {
|
// Sum of all weights of items and containers that are carried
|
||||||
|
'denormalizedStats.weightCarried': {
|
||||||
type: Number,
|
type: Number,
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
},
|
},
|
||||||
level: {
|
|
||||||
type: SimpleSchema.Integer,
|
|
||||||
defaultValue: 0,
|
|
||||||
},
|
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
defaultValue: 'pc',
|
defaultValue: 'pc',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { includes, cloneDeep } from 'lodash';
|
|||||||
// The computation memo is an in-memory data structure used only during the
|
// The computation memo is an in-memory data structure used only during the
|
||||||
// computation process
|
// computation process
|
||||||
export default class ComputationMemo {
|
export default class ComputationMemo {
|
||||||
constructor(props){
|
constructor(props, creature){
|
||||||
this.statsByVariableName = {};
|
this.statsByVariableName = {};
|
||||||
this.extraStatsByVariableName = {};
|
this.extraStatsByVariableName = {};
|
||||||
this.statsById = {};
|
this.statsById = {};
|
||||||
@@ -51,6 +51,15 @@ export default class ComputationMemo {
|
|||||||
this.addClassLevel(prop);
|
this.addClassLevel(prop);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
for (let name in creature.denormalizedStats){
|
||||||
|
if (!this.statsByVariableName[name]){
|
||||||
|
this.statsByVariableName[name] = {
|
||||||
|
variableName: name,
|
||||||
|
value: creature.denormalizedStats[name],
|
||||||
|
computationDetails: propDetailsByType.denormalizedStat(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
registerProperty(prop){
|
registerProperty(prop){
|
||||||
this.originalPropsById[prop._id] = cloneDeep(prop);
|
this.originalPropsById[prop._id] = cloneDeep(prop);
|
||||||
@@ -251,4 +260,10 @@ const propDetailsByType = {
|
|||||||
disabledByToggle: false,
|
disabledByToggle: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
denormalizedStat(){
|
||||||
|
return {
|
||||||
|
toggleAncestors: [],
|
||||||
|
disabledByToggle: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import computeMemo from '/imports/api/creature/computation/computeMemo.js';
|
|||||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||||
import writeAlteredProperties from '/imports/api/creature/computation/writeAlteredProperties.js';
|
import writeAlteredProperties from '/imports/api/creature/computation/writeAlteredProperties.js';
|
||||||
import writeCreatureVariables from '/imports/api/creature/computation/writeCreatureVariables.js';
|
import writeCreatureVariables from '/imports/api/creature/computation/writeCreatureVariables.js';
|
||||||
import { recomputeDamageMultipliersById } from '/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js'
|
import { recomputeDamageMultipliersById } from '/imports/api/creature/damageMultiplierDenormalise/recomputeDamageMultipliers.js';
|
||||||
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
|
||||||
export const recomputeCreature = new ValidatedMethod({
|
export const recomputeCreature = new ValidatedMethod({
|
||||||
|
|
||||||
@@ -17,8 +18,9 @@ export const recomputeCreature = new ValidatedMethod({
|
|||||||
}).validator(),
|
}).validator(),
|
||||||
|
|
||||||
run({charId}) {
|
run({charId}) {
|
||||||
|
let creature = Creatures.findOne(charId);
|
||||||
// Permission
|
// Permission
|
||||||
assertEditPermission(charId, this.userId);
|
assertEditPermission(creature, this.userId);
|
||||||
// Work, call this direcly if you are already in a method that has checked
|
// Work, call this direcly if you are already in a method that has checked
|
||||||
// for permission to edit a given character
|
// for permission to edit a given character
|
||||||
recomputeCreatureById(charId);
|
recomputeCreatureById(charId);
|
||||||
@@ -35,6 +37,11 @@ const calculationPropertyTypes = [
|
|||||||
'toggle',
|
'toggle',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function recomputeCreatureById(creatureId){
|
||||||
|
let creature = Creatures.findOne(creatureId);
|
||||||
|
recomputeCreatureByDoc(creature);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is the heart of DiceCloud. It recomputes a creature's stats,
|
* This function is the heart of DiceCloud. It recomputes a creature's stats,
|
||||||
* distilling down effects and proficiencies into the final stats that make up
|
* distilling down effects and proficiencies into the final stats that make up
|
||||||
@@ -71,14 +78,15 @@ const calculationPropertyTypes = [
|
|||||||
* - Mark the stat as computed
|
* - Mark the stat as computed
|
||||||
* - Write the computed results back to the database
|
* - Write the computed results back to the database
|
||||||
*/
|
*/
|
||||||
export function recomputeCreatureById(creatureId){
|
function recomputeCreatureByDoc(creature){
|
||||||
|
const creatureId = creature._id;
|
||||||
let props = getActiveProperties({
|
let props = getActiveProperties({
|
||||||
ancestorId: creatureId,
|
ancestorId: creatureId,
|
||||||
filter: {type: {$in: calculationPropertyTypes}},
|
filter: {type: {$in: calculationPropertyTypes}},
|
||||||
includeUntoggled: true,
|
includeUntoggled: true,
|
||||||
// TODO filter out expensive fields, particularly icon field
|
// TODO filter out expensive fields, particularly icon field
|
||||||
});
|
});
|
||||||
let computationMemo = new ComputationMemo(props);
|
let computationMemo = new ComputationMemo(props, creature);
|
||||||
computeMemo(computationMemo);
|
computeMemo(computationMemo);
|
||||||
writeAlteredProperties(computationMemo);
|
writeAlteredProperties(computationMemo);
|
||||||
writeCreatureVariables(computationMemo, creatureId);
|
writeCreatureVariables(computationMemo, creatureId);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|||||||
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
import { getUserTier } from '/imports/api/users/patreon/tiers.js';
|
||||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||||
import Creatures from '/imports/api/creature/Creatures.js';
|
import Creatures from '/imports/api/creature/Creatures.js';
|
||||||
|
import { recomputeCreatureById } from '/imports/api/creature/computation/recomputeCreature.js';
|
||||||
|
|
||||||
let Experiences = new Mongo.Collection('experiences');
|
let Experiences = new Mongo.Collection('experiences');
|
||||||
|
|
||||||
@@ -47,13 +48,19 @@ Experiences.attachSchema(ExperienceSchema);
|
|||||||
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
const insertExperienceForCreature = function({experience, creatureId, userId}){
|
||||||
assertEditPermission(creatureId, userId);
|
assertEditPermission(creatureId, userId);
|
||||||
if (experience.xp){
|
if (experience.xp){
|
||||||
Creatures.update(creatureId, {$inc: {xp: experience.xp}});
|
Creatures.update(creatureId, {$inc: {
|
||||||
|
'denormalizedStats.xp': experience.xp
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
if (experience.levels) {
|
if (experience.levels) {
|
||||||
Creatures.update(creatureId, {$inc: {xpLevels: experience.levels}});
|
Creatures.update(creatureId, {$inc: {
|
||||||
|
'denormalizedStats.milestoneLevels': experience.levels
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
experience.creatureId = creatureId;
|
experience.creatureId = creatureId;
|
||||||
return Experiences.insert(experience);
|
let id = Experiences.insert(experience);
|
||||||
|
recomputeCreatureById(creatureId);
|
||||||
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const insertExperience = new ValidatedMethod({
|
const insertExperience = new ValidatedMethod({
|
||||||
@@ -115,13 +122,19 @@ const removeExperience = new ValidatedMethod({
|
|||||||
let creatureId = experience.creatureId
|
let creatureId = experience.creatureId
|
||||||
assertEditPermission(creatureId, userId);
|
assertEditPermission(creatureId, userId);
|
||||||
if (experience.xp){
|
if (experience.xp){
|
||||||
Creatures.update(creatureId, {$inc: {xp: -experience.xp}});
|
Creatures.update(creatureId, {$inc: {
|
||||||
|
'denormalizedStats.xp': -experience.xp
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
if (experience.levels) {
|
if (experience.levels) {
|
||||||
Creatures.update(creatureId, {$inc: {xpLevels: -experience.levels}});
|
Creatures.update(creatureId, {$inc: {
|
||||||
|
'denormalizedStats.milestoneLevels': -experience.levels
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
experience.creatureId = creatureId;
|
experience.creatureId = creatureId;
|
||||||
return Experiences.remove(experienceId);
|
let numRemoved = Experiences.remove(experienceId);
|
||||||
|
recomputeCreatureById(creatureId);
|
||||||
|
return numRemoved;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,16 +160,20 @@ const recomputeExperiences = new ValidatedMethod({
|
|||||||
assertEditPermission(creatureId, userId);
|
assertEditPermission(creatureId, userId);
|
||||||
|
|
||||||
let xp = 0;
|
let xp = 0;
|
||||||
let xpLevels = 0;
|
let milestoneLevels = 0;
|
||||||
Experiences.find({
|
Experiences.find({
|
||||||
creatureId
|
creatureId
|
||||||
}, {
|
}, {
|
||||||
fields: {xp: 1, levels: 1}
|
fields: {xp: 1, levels: 1}
|
||||||
}).forEach(experience => {
|
}).forEach(experience => {
|
||||||
xp += experience.xp || 0;
|
xp += experience.xp || 0;
|
||||||
xpLevels += experience.levels || 0;
|
milestoneLevels += experience.levels || 0;
|
||||||
});
|
});
|
||||||
Creatures.update(creatureId, {$set: {xp, xpLevels}});
|
Creatures.update(creatureId, {$set: {
|
||||||
|
'denormalizedStats.xp': xp,
|
||||||
|
'denormalizedStats.milestoneLevels': milestoneLevels
|
||||||
|
}});
|
||||||
|
recomputeCreatureById(creatureId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,14 +28,23 @@
|
|||||||
>
|
>
|
||||||
Level {{ creature.variables.level.value }}
|
Level {{ creature.variables.level.value }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-list v-if="highestClassLevels.length">
|
<v-list>
|
||||||
<v-list-tile>
|
<v-list-tile>
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
<v-list-tile-title v-if="creature.xpLevels">
|
<v-list-tile-title
|
||||||
{{ creature.xpLevels }} Levels gained
|
v-if="
|
||||||
|
creature.variables.milestoneLevels &&
|
||||||
|
creature.variables.milestoneLevels.value
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ creature.variables.milestoneLevels.value }} Milestone levels
|
||||||
</v-list-tile-title>
|
</v-list-tile-title>
|
||||||
<v-list-tile-title v-else>
|
<v-list-tile-title v-else>
|
||||||
{{ creature.xp }} XP
|
{{
|
||||||
|
creature.variables.xp &&
|
||||||
|
creature.variables.xp.value ||
|
||||||
|
0
|
||||||
|
}} XP
|
||||||
</v-list-tile-title>
|
</v-list-tile-title>
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
@@ -161,7 +170,8 @@ export default {
|
|||||||
elementId: 'experience-add-button',
|
elementId: 'experience-add-button',
|
||||||
data: {
|
data: {
|
||||||
creatureIds: [this.creatureId],
|
creatureIds: [this.creatureId],
|
||||||
startAsMilestone: !!this.creature.xpLevels
|
startAsMilestone: this.creature.variables.milestoneLevels &&
|
||||||
|
!!this.creature.variables.milestoneLevels.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -171,6 +181,8 @@ export default {
|
|||||||
elementId: 'experience-info-button',
|
elementId: 'experience-info-button',
|
||||||
data: {
|
data: {
|
||||||
creatureId: this.creatureId,
|
creatureId: this.creatureId,
|
||||||
|
startAsMilestone: this.creature.variables.milestoneLevels &&
|
||||||
|
!!this.creature.variables.milestoneLevels.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,12 +8,6 @@
|
|||||||
@push="push"
|
@push="push"
|
||||||
@pull="pull"
|
@pull="pull"
|
||||||
/>
|
/>
|
||||||
<p
|
|
||||||
v-if="error"
|
|
||||||
class="error"
|
|
||||||
>
|
|
||||||
{{ error }}
|
|
||||||
</p>
|
|
||||||
<div
|
<div
|
||||||
slot="actions"
|
slot="actions"
|
||||||
class="layout row justify-end"
|
class="layout row justify-end"
|
||||||
@@ -21,7 +15,6 @@
|
|||||||
<v-btn
|
<v-btn
|
||||||
flat
|
flat
|
||||||
:disabled="!valid"
|
:disabled="!valid"
|
||||||
:loading="loading"
|
|
||||||
@click="insertExperience"
|
@click="insertExperience"
|
||||||
>
|
>
|
||||||
Insert
|
Insert
|
||||||
@@ -67,26 +60,20 @@ export default {
|
|||||||
schema: schema,
|
schema: schema,
|
||||||
validationContext: schema.newContext(),
|
validationContext: schema.newContext(),
|
||||||
debounceTime: 0,
|
debounceTime: 0,
|
||||||
loading: false,
|
|
||||||
error: undefined,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
insertExperience(){
|
insertExperience(){
|
||||||
this.loading = true;
|
|
||||||
let experience = this.schema.clean(this.model);
|
let experience = this.schema.clean(this.model);
|
||||||
insertExperience.call({
|
let id = insertExperience.call({
|
||||||
experience,
|
experience,
|
||||||
creatureIds: this.creatureIds,
|
creatureIds: this.creatureIds,
|
||||||
}, (error) => {
|
}, (error) => {
|
||||||
this.loading = false;
|
|
||||||
if (error){
|
if (error){
|
||||||
this.error = error.message || error;
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
} else {
|
|
||||||
this.$store.dispatch('popDialogStack');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.$store.dispatch('popDialogStack', id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,14 @@
|
|||||||
Experiences
|
Experiences
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
flat
|
||||||
|
data-id="experience-add-button"
|
||||||
|
@click="addExperience"
|
||||||
|
>
|
||||||
|
<v-icon>add</v-icon>
|
||||||
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
icon
|
icon
|
||||||
flat
|
flat
|
||||||
@@ -14,33 +22,56 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-tile
|
<v-slide-x-transition
|
||||||
v-for="experience in experiences"
|
group
|
||||||
:key="experience._id"
|
mode="out"
|
||||||
>
|
>
|
||||||
<v-list-tile-content>
|
<v-list-tile
|
||||||
<v-list-tile-title>
|
v-for="experience in experiences"
|
||||||
{{ experience.name }}
|
:key="experience._id"
|
||||||
</v-list-tile-title>
|
:data-id="experience._id"
|
||||||
</v-list-tile-content>
|
>
|
||||||
<v-list-tile-action>
|
<v-list-tile-action class="mr-3">
|
||||||
<v-btn
|
<v-list-tile-action-text>
|
||||||
icon
|
{{ formatDate(experience.date) }}
|
||||||
flat
|
</v-list-tile-action-text>
|
||||||
:loading="experiencesRemovalLoading.has(experience._id)"
|
</v-list-tile-action>
|
||||||
@click="removeExperience(experience._id)"
|
<v-list-tile-content>
|
||||||
>
|
<template v-if="experience.name">
|
||||||
<v-icon>delete</v-icon>
|
<v-list-tile-title>
|
||||||
</v-btn>
|
{{ experience.name }}
|
||||||
</v-list-tile-action>
|
</v-list-tile-title>
|
||||||
</v-list-tile>
|
<v-list-tile-sub-title>
|
||||||
|
{{ xpText(experience) }}
|
||||||
|
</v-list-tile-sub-title>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-list-tile-title>
|
||||||
|
{{ xpText(experience) }}
|
||||||
|
</v-list-tile-title>
|
||||||
|
</template>
|
||||||
|
</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-slide-x-transition>
|
||||||
</v-list>
|
</v-list>
|
||||||
</dialog-base>
|
</dialog-base>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { format } from 'date-fns';
|
||||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
import Experiences, { removeExperience, recomputeExperiences } from '/imports/api/creature/experience/Experiences.js';
|
import Experiences, { removeExperience, recomputeExperiences } from '/imports/api/creature/experience/Experiences.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
DialogBase,
|
DialogBase,
|
||||||
@@ -50,6 +81,9 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
startAsMilestone: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data(){ return {
|
data(){ return {
|
||||||
experiencesRemovalLoading: new Set(),
|
experiencesRemovalLoading: new Set(),
|
||||||
@@ -70,6 +104,21 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
xpText(experience){
|
||||||
|
let xpText = [];
|
||||||
|
if (experience.levels === 1){
|
||||||
|
xpText.push('1 Milestone level');
|
||||||
|
} else if (experience.levels){
|
||||||
|
xpText.push(`${experience.levels} Milestone levels`);
|
||||||
|
}
|
||||||
|
if (experience.xp || !experience.levels){
|
||||||
|
xpText.push(`${experience.xp || 0} XP`);
|
||||||
|
}
|
||||||
|
return xpText.join(', ');
|
||||||
|
},
|
||||||
|
formatDate(date){
|
||||||
|
return format(date, 'YYYY-MM-DD');
|
||||||
|
},
|
||||||
removeExperience(experienceId){
|
removeExperience(experienceId){
|
||||||
this.experiencesRemovalLoading.add(experienceId);
|
this.experiencesRemovalLoading.add(experienceId);
|
||||||
removeExperience.call({experienceId}, (error) => {
|
removeExperience.call({experienceId}, (error) => {
|
||||||
@@ -84,6 +133,19 @@ export default {
|
|||||||
if (error) console.error(error);
|
if (error) console.error(error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
addExperience(){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'experience-insert-dialog',
|
||||||
|
elementId: 'experience-add-button',
|
||||||
|
data: {
|
||||||
|
creatureIds: [this.creatureId],
|
||||||
|
startAsMilestone: this.startAsMilestone,
|
||||||
|
},
|
||||||
|
callback(id){
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user