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 { 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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
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">
|
<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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user