Added slotfiller property type to increase control over slot filling

This commit is contained in:
Stefan Zermatten
2020-10-15 14:54:58 +02:00
parent 8e9405b5ad
commit ed17d9e2d2
10 changed files with 214 additions and 16 deletions

View File

@@ -0,0 +1,38 @@
// SlotFiller fillers specifically fill a slot with a bit more control than
// other properties
import SimpleSchema from 'simpl-schema';
let SlotFillerSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
picture: {
type: String,
optional: true,
},
description: {
type: String,
optional: true,
},
// Overrides the type when searching for properties
slotFillerType: {
type: String,
optional: true,
},
// Fill more than one quantity in a slot, like feats and ability score
// improvements, filtered out of UI if there isn't space in quantityExpected
slotQuantityFilled: {
type: SimpleSchema.Integer,
defaultValue: 1,
min: 0,
},
// Filters out of UI if condition isn't met, but isn't otherwise enforced
slotFillerCondition: {
type: String,
optional: true,
},
});
export { SlotFillerSchema };

View File

@@ -18,6 +18,7 @@ import { RollSchema } from '/imports/api/properties/Rolls.js';
import { ComputedSavingThrowSchema } from '/imports/api/properties/SavingThrows.js'; import { ComputedSavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
import { ComputedSkillSchema } from '/imports/api/properties/Skills.js'; import { ComputedSkillSchema } from '/imports/api/properties/Skills.js';
import { ComputedSlotSchema } from '/imports/api/properties/Slots.js'; import { ComputedSlotSchema } from '/imports/api/properties/Slots.js';
import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
import { ComputedSpellSchema } from '/imports/api/properties/Spells.js'; import { ComputedSpellSchema } from '/imports/api/properties/Spells.js';
import { ComputedSpellListSchema } from '/imports/api/properties/SpellLists.js'; import { ComputedSpellListSchema } from '/imports/api/properties/SpellLists.js';
import { ToggleSchema } from '/imports/api/properties/Toggles.js'; import { ToggleSchema } from '/imports/api/properties/Toggles.js';
@@ -36,10 +37,11 @@ const propertySchemasIndex = {
folder: FolderSchema, folder: FolderSchema,
note: NoteSchema, note: NoteSchema,
proficiency: ProficiencySchema, proficiency: ProficiencySchema,
propertySlot: ComputedSlotSchema,
roll: RollSchema, roll: RollSchema,
savingThrow: ComputedSavingThrowSchema, savingThrow: ComputedSavingThrowSchema,
skill: ComputedSkillSchema, skill: ComputedSkillSchema,
propertySlot: ComputedSlotSchema, slotFiller: SlotFillerSchema,
spellList: ComputedSpellListSchema, spellList: ComputedSpellListSchema,
spell: ComputedSpellSchema, spell: ComputedSpellSchema,
toggle: ToggleSchema, toggle: ToggleSchema,

View File

@@ -16,6 +16,7 @@ import { RollSchema } from '/imports/api/properties/Rolls.js';
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js'; import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
import { SkillSchema } from '/imports/api/properties/Skills.js'; import { SkillSchema } from '/imports/api/properties/Skills.js';
import { SlotSchema } from '/imports/api/properties/Slots.js'; import { SlotSchema } from '/imports/api/properties/Slots.js';
import { SlotFillerSchema } from '/imports/api/properties/SlotFillers.js';
import { SpellListSchema } from '/imports/api/properties/SpellLists.js'; import { SpellListSchema } from '/imports/api/properties/SpellLists.js';
import { SpellSchema } from '/imports/api/properties/Spells.js'; import { SpellSchema } from '/imports/api/properties/Spells.js';
import { ToggleSchema } from '/imports/api/properties/Toggles.js'; import { ToggleSchema } from '/imports/api/properties/Toggles.js';
@@ -36,10 +37,11 @@ const propertySchemasIndex = {
folder: FolderSchema, folder: FolderSchema,
note: NoteSchema, note: NoteSchema,
proficiency: ProficiencySchema, proficiency: ProficiencySchema,
propertySlot: SlotSchema,
roll: RollSchema, roll: RollSchema,
savingThrow: SavingThrowSchema, savingThrow: SavingThrowSchema,
skill: SkillSchema, skill: SkillSchema,
propertySlot: SlotSchema, slotFiller: SlotFillerSchema,
spellList: SpellListSchema, spellList: SpellListSchema,
spell: SpellSchema, spell: SpellSchema,
toggle: ToggleSchema, toggle: ToggleSchema,

View File

@@ -75,6 +75,10 @@ const PROPERTIES = Object.freeze({
icon: 'tab_unselected', icon: 'tab_unselected',
name: 'Slot' name: 'Slot'
}, },
slotFiller: {
icon: 'picture_in_picture',
name: 'Slot filler'
},
spellList: { spellList: {
icon: '$vuetify.icons.spell_list', icon: '$vuetify.icons.spell_list',
name: 'Spell list' name: 'Spell list'

View File

@@ -33,10 +33,17 @@ Meteor.publish('slotFillers', function(slotId){
// Build a filter for nodes in those libraries that match the slot // Build a filter for nodes in those libraries that match the slot
let filter = { let filter = {
'ancestors.id': {$in: libraryIds}, 'ancestors.id': {$in: libraryIds},
'tags': {$all: slot.slotTags},
}; };
if (slot.tags.length){
filter.tags = {$all: slot.slotTags};
}
if (slot.slotType){ if (slot.slotType){
filter.type = slot.slotType; filter.$or = [{
type: slot.slotType
},{
type: 'slotFiller',
slotFillerType: slot.slotType,
}];
} }
return LibraryNodes.find(filter); return LibraryNodes.find(filter);
}); });

View File

@@ -73,11 +73,14 @@
</template> </template>
<script> <script>
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 LibraryNodes from '/imports/api/library/LibraryNodes.js'; import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue'; import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
import { getPropertyName } from '/imports/constants/PROPERTIES.js'; import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import { parse, CompilationContext } from '/imports/parser/parser.js';
export default { export default {
components: { components: {
DialogBase, DialogBase,
@@ -87,7 +90,15 @@ export default {
slotId: { slotId: {
type: String, type: String,
required: true, required: true,
} },
creatureId: {
type: String,
required: true,
},
numToFill: {
type: Number,
required: true,
},
}, },
data(){return { data(){return {
selectedNode: undefined, selectedNode: undefined,
@@ -109,14 +120,41 @@ export default {
model(){ model(){
return CreatureProperties.findOne(this.slotId); return CreatureProperties.findOne(this.slotId);
}, },
creature(){
return Creatures.findOne(this.creatureId);
},
libraryNodes(){ libraryNodes(){
let filter = { let filter = {};
tags: {$all: this.model.slotTags}, if (this.model.tags.length){
}; filter.tags = {$all: this.model.slotTags};
}
if (this.model.slotType){ if (this.model.slotType){
filter.type = this.model.slotType; filter.$or = [{
type: this.model.slotType
},{
type: 'slotFiller',
slotFillerType: this.model.slotType,
}];
} }
let nodes = LibraryNodes.find(filter).fetch(); let nodes = LibraryNodes.find(filter).fetch();
// Filter out slotFillers whose condition isn't met or are too big to fit
// the quantity to fill
nodes = nodes.filter(node => {
if (node.type === 'slotFiller'){
if (node.slotFillerCondition){
let context = new CompilationContext();
let conditionResult = parse(node.slotFillerCondition)
.reduce(this.creature.variables, context);
if (conditionResult && !conditionResult.value) return false;
}
console.log({numToFill: this.numToFill, node});
if (this.numToFill > 0 && node.slotQuantityFilled > this.numToFill){
return false;
}
}
return true;
});
// Filter out slotFillers whose
if (nodes.length === 1) this.selectedNode = nodes[0]; if (nodes.length === 1) this.selectedNode = nodes[0];
return nodes; return nodes;
}, },

View File

@@ -9,7 +9,7 @@
{{ slot.name }} {{ slot.name }}
<v-spacer /> <v-spacer />
<span v-if="slot.quantityExpected > 1"> <span v-if="slot.quantityExpected > 1">
{{ slot.children.length }} / {{ slot.quantityExpected }} {{ slot.totalFilled }} / {{ slot.quantityExpected }}
</span> </span>
</h3> </h3>
<div <div
@@ -33,11 +33,11 @@
</v-btn> </v-btn>
</div> </div>
<v-btn <v-btn
v-if="!slot.quantityExpected || slot.quantityExpected > slot.children.length" v-if="!slot.quantityExpected || slot.quantityExpected > slot.totalFilled"
icon icon
:data-id="`slot-add-button-${slot._id}`" :data-id="`slot-add-button-${slot._id}`"
style="background-color: inherit;" style="background-color: inherit;"
@click="fillSlot(slot._id)" @click="fillSlot(slot)"
> >
<v-icon>add</v-icon> <v-icon>add</v-icon>
</v-btn> </v-btn>
@@ -64,11 +64,19 @@ export default {
}, },
}, },
methods: { methods: {
fillSlot(slotId){ fillSlot(slot){
let slotId = slot._id;
let creatureId = this.creatureId;
let numToFill = slot.quantityExpected === 0 ?
0 : slot.quantityExpected - slot.totalFilled;
this.$store.commit('pushDialogStack', { this.$store.commit('pushDialogStack', {
component: 'slot-fill-dialog', component: 'slot-fill-dialog',
elementId: `slot-add-button-${slotId}`, elementId: `slot-add-button-${slotId}`,
data: {slotId}, data: {
slotId,
creatureId,
numToFill,
},
callback(node){ callback(node){
if(!node) return; if(!node) return;
let newPropertyId = insertPropertyFromLibraryNode.call({ let newPropertyId = insertPropertyFromLibraryNode.call({
@@ -105,6 +113,14 @@ export default {
'parent.id': slot._id, 'parent.id': slot._id,
removed: {$ne: true}, removed: {$ne: true},
}).fetch(); }).fetch();
slot.totalFilled = 0;
slot.children.forEach(child => {
if (child.type === 'slotFiller'){
slot.totalFilled += child.slotQuantityFilled;
} else {
slot.totalFilled++;
}
});
return slot; return slot;
}); });
}, },

View File

@@ -0,0 +1,88 @@
<template lang="html">
<div class="slot-filler-form">
<div class="layout column align-center">
<icon-picker
label="Icon"
:value="model.icon"
:error-messages="errors.icon"
@change="change('icon', ...arguments)"
/>
</div>
<text-field
ref="focusFirst"
label="Name"
:value="model.name"
:error-messages="errors.name"
@change="change('name', ...arguments)"
/>
<text-field
label="Picture URL"
hint="A link to an image representing this property"
:value="model.picture"
:error-messages="errors.picture"
@change="change('picture', ...arguments)"
/>
<smart-select
label="Type"
style="flex-basis: 300px;"
clearable
:items="slotTypes"
:value="model.slotFillerType"
:error-messages="errors.slotFillerType"
@change="change('slotFillerType', ...arguments)"
/>
<smart-combobox
label="Tags"
multiple
chips
deletable-chips
:value="model.tags"
:error-messages="errors.tags"
@change="change('tags', ...arguments)"
/>
<text-field
label="Quantity"
type="number"
min="0"
hint="How many properties this counts as when filling a slot"
:value="model.slotQuantityFilled"
:error-messages="errors.slotQuantityFilled"
@change="change('slotQuantityFilled', ...arguments)"
/>
<text-field
label="Condition"
hint="A caclulation to determine if this can be added to the character"
placeholder="Always active"
:value="model.slotFillerCondition"
:error-messages="errors.slotFillerCondition"
@change="change('slotFillerCondition', ...arguments)"
/>
<calculation-error-list :errors="model.slotConditionErrors" />
<text-area
label="Description"
:value="model.description"
:error-messages="errors.description"
@change="change('description', ...arguments)"
/>
</div>
</template>
<script>
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
import PROPERTIES from '/imports/constants/PROPERTIES.js';
export default {
components: {
CalculationErrorList,
},
mixins: [propertyFormMixin],
data(){
let slotTypes = [];
for (let key in PROPERTIES){
slotTypes.push({text: PROPERTIES[key].name, value: key});
}
return {slotTypes};
},
};
</script>

View File

@@ -1,5 +1,5 @@
<template lang="html"> <template lang="html">
<div class="spell-form"> <div class="slot-form">
<text-field <text-field
ref="focusFirst" ref="focusFirst"
label="Name" label="Name"
@@ -10,6 +10,7 @@
<smart-select <smart-select
label="Type" label="Type"
style="flex-basis: 300px;" style="flex-basis: 300px;"
clearable
:items="slotTypes" :items="slotTypes"
:value="model.slotType" :value="model.slotType"
:error-messages="errors.slotType" :error-messages="errors.slotType"

View File

@@ -17,6 +17,7 @@ import RollForm from '/imports/ui/properties/forms/RollForm.vue';
import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue'; import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
import SkillForm from '/imports/ui/properties/forms/SkillForm.vue'; import SkillForm from '/imports/ui/properties/forms/SkillForm.vue';
import SlotForm from '/imports/ui/properties/forms/SlotForm.vue'; import SlotForm from '/imports/ui/properties/forms/SlotForm.vue';
import SlotFillerForm from '/imports/ui/properties/forms/SlotFillerForm.vue';
import SpellListForm from '/imports/ui/properties/forms/SpellListForm.vue'; import SpellListForm from '/imports/ui/properties/forms/SpellListForm.vue';
import SpellForm from '/imports/ui/properties/forms/SpellForm.vue'; import SpellForm from '/imports/ui/properties/forms/SpellForm.vue';
import ToggleForm from '/imports/ui/properties/forms/ToggleForm.vue'; import ToggleForm from '/imports/ui/properties/forms/ToggleForm.vue';
@@ -37,10 +38,11 @@ export default {
item: ItemForm, item: ItemForm,
note: NoteForm, note: NoteForm,
proficiency: ProficiencyForm, proficiency: ProficiencyForm,
propertySlot: SlotForm,
roll: RollForm, roll: RollForm,
savingThrow: SavingThrowForm, savingThrow: SavingThrowForm,
skill: SkillForm, skill: SkillForm,
propertySlot: SlotForm, slotFiller: SlotFillerForm,
spellList: SpellListForm, spellList: SpellListForm,
spell: SpellForm, spell: SpellForm,
toggle: ToggleForm, toggle: ToggleForm,