Added slotfiller property type to increase control over slot filling
This commit is contained in:
38
app/imports/api/properties/SlotFillers.js
Normal file
38
app/imports/api/properties/SlotFillers.js
Normal 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 };
|
||||
@@ -18,6 +18,7 @@ import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||
import { ComputedSavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||
import { ComputedSkillSchema } from '/imports/api/properties/Skills.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 { ComputedSpellListSchema } from '/imports/api/properties/SpellLists.js';
|
||||
import { ToggleSchema } from '/imports/api/properties/Toggles.js';
|
||||
@@ -36,10 +37,11 @@ const propertySchemasIndex = {
|
||||
folder: FolderSchema,
|
||||
note: NoteSchema,
|
||||
proficiency: ProficiencySchema,
|
||||
propertySlot: ComputedSlotSchema,
|
||||
roll: RollSchema,
|
||||
savingThrow: ComputedSavingThrowSchema,
|
||||
skill: ComputedSkillSchema,
|
||||
propertySlot: ComputedSlotSchema,
|
||||
slotFiller: SlotFillerSchema,
|
||||
spellList: ComputedSpellListSchema,
|
||||
spell: ComputedSpellSchema,
|
||||
toggle: ToggleSchema,
|
||||
|
||||
@@ -16,6 +16,7 @@ import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||
import { SavingThrowSchema } from '/imports/api/properties/SavingThrows.js';
|
||||
import { SkillSchema } from '/imports/api/properties/Skills.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 { SpellSchema } from '/imports/api/properties/Spells.js';
|
||||
import { ToggleSchema } from '/imports/api/properties/Toggles.js';
|
||||
@@ -36,10 +37,11 @@ const propertySchemasIndex = {
|
||||
folder: FolderSchema,
|
||||
note: NoteSchema,
|
||||
proficiency: ProficiencySchema,
|
||||
propertySlot: SlotSchema,
|
||||
roll: RollSchema,
|
||||
savingThrow: SavingThrowSchema,
|
||||
skill: SkillSchema,
|
||||
propertySlot: SlotSchema,
|
||||
slotFiller: SlotFillerSchema,
|
||||
spellList: SpellListSchema,
|
||||
spell: SpellSchema,
|
||||
toggle: ToggleSchema,
|
||||
|
||||
@@ -75,6 +75,10 @@ const PROPERTIES = Object.freeze({
|
||||
icon: 'tab_unselected',
|
||||
name: 'Slot'
|
||||
},
|
||||
slotFiller: {
|
||||
icon: 'picture_in_picture',
|
||||
name: 'Slot filler'
|
||||
},
|
||||
spellList: {
|
||||
icon: '$vuetify.icons.spell_list',
|
||||
name: 'Spell list'
|
||||
|
||||
@@ -33,10 +33,17 @@ Meteor.publish('slotFillers', function(slotId){
|
||||
// Build a filter for nodes in those libraries that match the slot
|
||||
let filter = {
|
||||
'ancestors.id': {$in: libraryIds},
|
||||
'tags': {$all: slot.slotTags},
|
||||
};
|
||||
if (slot.tags.length){
|
||||
filter.tags = {$all: slot.slotTags};
|
||||
}
|
||||
if (slot.slotType){
|
||||
filter.type = slot.slotType;
|
||||
filter.$or = [{
|
||||
type: slot.slotType
|
||||
},{
|
||||
type: 'slotFiller',
|
||||
slotFillerType: slot.slotType,
|
||||
}];
|
||||
}
|
||||
return LibraryNodes.find(filter);
|
||||
});
|
||||
|
||||
@@ -73,11 +73,14 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Creatures from '/imports/api/creature/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
|
||||
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import { parse, CompilationContext } from '/imports/parser/parser.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DialogBase,
|
||||
@@ -87,7 +90,15 @@ export default {
|
||||
slotId: {
|
||||
type: String,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
creatureId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
numToFill: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
selectedNode: undefined,
|
||||
@@ -109,14 +120,41 @@ export default {
|
||||
model(){
|
||||
return CreatureProperties.findOne(this.slotId);
|
||||
},
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId);
|
||||
},
|
||||
libraryNodes(){
|
||||
let filter = {
|
||||
tags: {$all: this.model.slotTags},
|
||||
};
|
||||
let filter = {};
|
||||
if (this.model.tags.length){
|
||||
filter.tags = {$all: this.model.slotTags};
|
||||
}
|
||||
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();
|
||||
// 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];
|
||||
return nodes;
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
{{ slot.name }}
|
||||
<v-spacer />
|
||||
<span v-if="slot.quantityExpected > 1">
|
||||
{{ slot.children.length }} / {{ slot.quantityExpected }}
|
||||
{{ slot.totalFilled }} / {{ slot.quantityExpected }}
|
||||
</span>
|
||||
</h3>
|
||||
<div
|
||||
@@ -33,11 +33,11 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn
|
||||
v-if="!slot.quantityExpected || slot.quantityExpected > slot.children.length"
|
||||
v-if="!slot.quantityExpected || slot.quantityExpected > slot.totalFilled"
|
||||
icon
|
||||
:data-id="`slot-add-button-${slot._id}`"
|
||||
style="background-color: inherit;"
|
||||
@click="fillSlot(slot._id)"
|
||||
@click="fillSlot(slot)"
|
||||
>
|
||||
<v-icon>add</v-icon>
|
||||
</v-btn>
|
||||
@@ -64,11 +64,19 @@ export default {
|
||||
},
|
||||
},
|
||||
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', {
|
||||
component: 'slot-fill-dialog',
|
||||
elementId: `slot-add-button-${slotId}`,
|
||||
data: {slotId},
|
||||
data: {
|
||||
slotId,
|
||||
creatureId,
|
||||
numToFill,
|
||||
},
|
||||
callback(node){
|
||||
if(!node) return;
|
||||
let newPropertyId = insertPropertyFromLibraryNode.call({
|
||||
@@ -105,6 +113,14 @@ export default {
|
||||
'parent.id': slot._id,
|
||||
removed: {$ne: true},
|
||||
}).fetch();
|
||||
slot.totalFilled = 0;
|
||||
slot.children.forEach(child => {
|
||||
if (child.type === 'slotFiller'){
|
||||
slot.totalFilled += child.slotQuantityFilled;
|
||||
} else {
|
||||
slot.totalFilled++;
|
||||
}
|
||||
});
|
||||
return slot;
|
||||
});
|
||||
},
|
||||
|
||||
88
app/imports/ui/properties/forms/SlotFillerForm.vue
Normal file
88
app/imports/ui/properties/forms/SlotFillerForm.vue
Normal 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>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template lang="html">
|
||||
<div class="spell-form">
|
||||
<div class="slot-form">
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
@@ -10,6 +10,7 @@
|
||||
<smart-select
|
||||
label="Type"
|
||||
style="flex-basis: 300px;"
|
||||
clearable
|
||||
:items="slotTypes"
|
||||
:value="model.slotType"
|
||||
:error-messages="errors.slotType"
|
||||
|
||||
@@ -17,6 +17,7 @@ import RollForm from '/imports/ui/properties/forms/RollForm.vue';
|
||||
import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
|
||||
import SkillForm from '/imports/ui/properties/forms/SkillForm.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 SpellForm from '/imports/ui/properties/forms/SpellForm.vue';
|
||||
import ToggleForm from '/imports/ui/properties/forms/ToggleForm.vue';
|
||||
@@ -37,10 +38,11 @@ export default {
|
||||
item: ItemForm,
|
||||
note: NoteForm,
|
||||
proficiency: ProficiencyForm,
|
||||
propertySlot: SlotForm,
|
||||
roll: RollForm,
|
||||
savingThrow: SavingThrowForm,
|
||||
skill: SkillForm,
|
||||
propertySlot: SlotForm,
|
||||
slotFiller: SlotFillerForm,
|
||||
spellList: SpellListForm,
|
||||
spell: SpellForm,
|
||||
toggle: ToggleForm,
|
||||
|
||||
Reference in New Issue
Block a user