Added spellcasting to the stats page, click the icon next to a spell slot to cast
This commit is contained in:
@@ -22,6 +22,7 @@ import { storedIconsSchema } from '/imports/api/icons/Icons.js';
|
|||||||
import { reorderDocs } from '/imports/api/parenting/order.js';
|
import { reorderDocs } from '/imports/api/parenting/order.js';
|
||||||
|
|
||||||
import '/imports/api/creature/actions/doAction.js';
|
import '/imports/api/creature/actions/doAction.js';
|
||||||
|
import '/imports/api/creature/actions/castSpellWithSlot.js';
|
||||||
import '/imports/api/creature/creatureProperties/manageEquipment.js';
|
import '/imports/api/creature/creatureProperties/manageEquipment.js';
|
||||||
|
|
||||||
let CreatureProperties = new Mongo.Collection('creatureProperties');
|
let CreatureProperties = new Mongo.Collection('creatureProperties');
|
||||||
|
|||||||
75
app/imports/api/creature/actions/castSpellWithSlot.js
Normal file
75
app/imports/api/creature/actions/castSpellWithSlot.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import SimpleSchema from 'simpl-schema';
|
||||||
|
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||||
|
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||||
|
import CreatureProperties, { getCreature, damagePropertyWork } from '/imports/api/creature/CreatureProperties.js';
|
||||||
|
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||||
|
import { recomputeCreatureByDoc } from '/imports/api/creature/computation/recomputeCreature.js';
|
||||||
|
import { doActionWork } from '/imports/api/creature/actions/doAction.js';
|
||||||
|
|
||||||
|
const castSpellWithSlot = new ValidatedMethod({
|
||||||
|
name: 'creatureProperties.castSpellWithSlot',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
spellId: SimpleSchema.RegEx.Id,
|
||||||
|
slotId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
targetId: {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 10,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({spellId, slotId, targetId}) {
|
||||||
|
let spell = CreatureProperties.findOne(spellId);
|
||||||
|
// Check permissions
|
||||||
|
let creature = getCreature(spell);
|
||||||
|
assertEditPermission(creature, this.userId);
|
||||||
|
let target = undefined;
|
||||||
|
if (targetId) {
|
||||||
|
target = getCreature(targetId);
|
||||||
|
assertEditPermission(target, this.userId);
|
||||||
|
}
|
||||||
|
let slotLevel = spell.level || 0;
|
||||||
|
if (slotLevel !== 0){
|
||||||
|
let slot = CreatureProperties.findOne(slotId);
|
||||||
|
if (!slot){
|
||||||
|
throw new Meteor.Error('No slot',
|
||||||
|
'Slot not found to cast spell');
|
||||||
|
}
|
||||||
|
if (!slot.currentValue){
|
||||||
|
throw new Meteor.Error('No slot',
|
||||||
|
'Slot depleted');
|
||||||
|
}
|
||||||
|
if (!(slot.spellSlotLevelValue >= spell.level)){
|
||||||
|
throw new Meteor.Error('Slot too small',
|
||||||
|
'Slot is not large enough to cast spell');
|
||||||
|
}
|
||||||
|
slotLevel = slot.spellSlotLevelValue;
|
||||||
|
damagePropertyWork({
|
||||||
|
property: slot,
|
||||||
|
operation: 'increment',
|
||||||
|
value: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
doActionWork({
|
||||||
|
action: spell,
|
||||||
|
context: {slotLevel},
|
||||||
|
creature,
|
||||||
|
target,
|
||||||
|
});
|
||||||
|
// Note this only recomputes the top-level creature, not the nearest one
|
||||||
|
recomputeCreatureByDoc(creature);
|
||||||
|
if (target){
|
||||||
|
recomputeCreatureByDoc(target);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default castSpellWithSlot;
|
||||||
@@ -41,8 +41,7 @@ const doAction = new ValidatedMethod({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function doActionWork({action, creature, target}){
|
export function doActionWork({action, creature, target, context = {}}){
|
||||||
let actionContext = {};
|
|
||||||
let decendantForest = nodesToTree({
|
let decendantForest = nodesToTree({
|
||||||
collection: CreatureProperties,
|
collection: CreatureProperties,
|
||||||
ancestorId: action._id,
|
ancestorId: action._id,
|
||||||
@@ -53,9 +52,9 @@ function doActionWork({action, creature, target}){
|
|||||||
}];
|
}];
|
||||||
applyProperties({
|
applyProperties({
|
||||||
forest: startingForest,
|
forest: startingForest,
|
||||||
|
actionContext: context,
|
||||||
creature,
|
creature,
|
||||||
target,
|
target,
|
||||||
actionContext
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,15 +149,18 @@
|
|||||||
class="spell-slots"
|
class="spell-slots"
|
||||||
>
|
>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-list>
|
<v-list
|
||||||
|
two-line
|
||||||
|
subheader
|
||||||
|
>
|
||||||
<v-subheader>Spell Slots</v-subheader>
|
<v-subheader>Spell Slots</v-subheader>
|
||||||
<spell-slot-list-tile
|
<spell-slot-list-tile
|
||||||
v-for="spellSlot in spellSlots"
|
v-for="spellSlot in spellSlots"
|
||||||
:key="spellSlot._id"
|
:key="spellSlot._id"
|
||||||
v-bind="spellSlot"
|
:model="spellSlot"
|
||||||
:data-id="spellSlot._id"
|
:data-id="spellSlot._id"
|
||||||
@click="clickProperty({_id: spellSlot._id})"
|
@click="clickProperty({_id: spellSlot._id})"
|
||||||
@change="e => incrementChange(spellSlot._id, e)"
|
@cast="castSpellWithSlot(spellSlot._id)"
|
||||||
/>
|
/>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
@@ -320,6 +323,7 @@
|
|||||||
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
|
import ActionCard from '/imports/ui/properties/components/actions/ActionCard.vue';
|
||||||
import RestButton from '/imports/ui/creature/RestButton.vue';
|
import RestButton from '/imports/ui/creature/RestButton.vue';
|
||||||
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||||
|
import castSpellWithSlot from '/imports/api/creature/actions/castSpellWithSlot.js';
|
||||||
|
|
||||||
const getProperties = function(creature, filter,){
|
const getProperties = function(creature, filter,){
|
||||||
if (!creature) return;
|
if (!creature) return;
|
||||||
@@ -448,6 +452,22 @@
|
|||||||
softRemoveProperty.call({_id}, error => {
|
softRemoveProperty.call({_id}, error => {
|
||||||
if (error) console.error(error);
|
if (error) console.error(error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
castSpellWithSlot(slotId){
|
||||||
|
this.$store.commit('pushDialogStack', {
|
||||||
|
component: 'cast-spell-with-slot-dialog',
|
||||||
|
elementId: `spell-slot-cast-btn-${slotId}`,
|
||||||
|
data: {
|
||||||
|
creatureId: this.creatureId,
|
||||||
|
slotId,
|
||||||
|
},
|
||||||
|
callback({spellId, slotId} = {}){
|
||||||
|
if (!spellId) return;
|
||||||
|
castSpellWithSlot.call({spellId, slotId}, error => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import CastSpellWithSlotDialog from '/imports/ui/properties/components/spells/CastSpellWithSlotDialog.vue';
|
||||||
import CreatureFormDialog from '/imports/ui/creature/CreatureFormDialog.vue';
|
import CreatureFormDialog from '/imports/ui/creature/CreatureFormDialog.vue';
|
||||||
import CreaturePropertyCreationDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue';
|
import CreaturePropertyCreationDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyCreationDialog.vue';
|
||||||
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
import CreaturePropertyDialog from '/imports/ui/creature/creatureProperties/CreaturePropertyDialog.vue'
|
||||||
@@ -19,6 +20,7 @@ import TierTooLowDialog from '/imports/ui/user/TierTooLowDialog.vue';
|
|||||||
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
import UsernameDialog from '/imports/ui/user/UsernameDialog.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
CastSpellWithSlotDialog,
|
||||||
CreatureFormDialog,
|
CreatureFormDialog,
|
||||||
CreaturePropertyCreationDialog,
|
CreaturePropertyCreationDialog,
|
||||||
CreaturePropertyDialog,
|
CreaturePropertyDialog,
|
||||||
|
|||||||
@@ -1,86 +1,56 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-list-tile
|
<v-list-tile
|
||||||
class="spell-slot-list-tile"
|
class="spell-slot-list-tile"
|
||||||
:class="{hover}"
|
v-on="hasClickListener ? {click} : {}"
|
||||||
>
|
>
|
||||||
<v-list-tile-action>
|
<v-list-tile-content>
|
||||||
<div
|
<v-list-tile-title>
|
||||||
v-if="value > 4"
|
|
||||||
class="layout row align-center"
|
|
||||||
>
|
|
||||||
<div class="buttons layout column justify-center">
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
small
|
|
||||||
:disabled="
|
|
||||||
currentValue >= value ||
|
|
||||||
context.editPermission === false
|
|
||||||
"
|
|
||||||
@click="increment(1)"
|
|
||||||
>
|
|
||||||
<v-icon>arrow_drop_up</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
small
|
|
||||||
:disabled="
|
|
||||||
currentValue <= 0 ||
|
|
||||||
context.editPermission === false
|
|
||||||
"
|
|
||||||
@click="increment(-1)"
|
|
||||||
>
|
|
||||||
<v-icon>arrow_drop_down</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
|
v-if="model.value > 4"
|
||||||
class="layout row value"
|
class="layout row value"
|
||||||
style="align-items: baseline;"
|
style="align-items: baseline;"
|
||||||
>
|
>
|
||||||
<div class="display-1">
|
<div
|
||||||
{{ currentValue }}
|
style="font-weight: 500; font-size: 24px"
|
||||||
|
class="current-value"
|
||||||
|
>
|
||||||
|
{{ model.currentValue }}
|
||||||
</div>
|
</div>
|
||||||
<div class="title ml-2 max-value">
|
<div class="ml-2 max-value">
|
||||||
/{{ value }}
|
/{{ model.value }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div
|
||||||
|
v-else
|
||||||
<div
|
class="layout row align-center slot-bubbles"
|
||||||
v-else
|
|
||||||
class="layout row align-center justify-end slot-buttons"
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
v-for="i in value"
|
|
||||||
:key="i"
|
|
||||||
icon
|
|
||||||
small
|
|
||||||
:disabled="
|
|
||||||
!(i === currentValue || i === currentValue + 1) ||
|
|
||||||
context.editPermission === false
|
|
||||||
"
|
|
||||||
@click="increment(i > currentValue ? 1 : -1)"
|
|
||||||
>
|
>
|
||||||
<v-icon>
|
<v-icon
|
||||||
|
v-for="i in model.value"
|
||||||
|
:key="i"
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
i > currentValue ?
|
i > model.currentValue ?
|
||||||
'radio_button_unchecked' :
|
'radio_button_unchecked' :
|
||||||
'radio_button_checked'
|
'radio_button_checked'
|
||||||
}}
|
}}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
</v-btn>
|
</div>
|
||||||
</div>
|
|
||||||
</v-list-tile-action>
|
|
||||||
|
|
||||||
<v-list-tile-content
|
|
||||||
class="content ml-2"
|
|
||||||
@click="click"
|
|
||||||
@mouseover="hover = true"
|
|
||||||
@mouseleave="hover = false"
|
|
||||||
>
|
|
||||||
<v-list-tile-title>
|
|
||||||
{{ name }}
|
|
||||||
</v-list-tile-title>
|
</v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>
|
||||||
|
{{ model.name }}
|
||||||
|
</v-list-tile-sub-title>
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-avatar v-if="!hideCastButton">
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
flat
|
||||||
|
class="primary--text"
|
||||||
|
:data-id="`spell-slot-cast-btn-${model._id}`"
|
||||||
|
@click.stop="$emit('cast')"
|
||||||
|
>
|
||||||
|
<v-icon>$vuetify.icons.spell</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-tile-avatar>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -88,18 +58,13 @@
|
|||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
_id: String,
|
model: {
|
||||||
name: String,
|
type: Object,
|
||||||
color: String,
|
required: true,
|
||||||
value: Number,
|
},
|
||||||
damage: {
|
dark: Boolean,
|
||||||
type: Number,
|
hideCastButton: Boolean,
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data(){ return{
|
|
||||||
hover: false,
|
|
||||||
}},
|
|
||||||
inject: {
|
inject: {
|
||||||
context: { default: {} }
|
context: { default: {} }
|
||||||
},
|
},
|
||||||
@@ -107,15 +72,15 @@ export default {
|
|||||||
currentValue(){
|
currentValue(){
|
||||||
return this.value - this.damage;
|
return this.value - this.damage;
|
||||||
},
|
},
|
||||||
|
hasClickListener(){
|
||||||
|
return this.$listeners && !!this.$listeners.click;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
signed: numberToSignedString,
|
signed: numberToSignedString,
|
||||||
click(e){
|
click(e){
|
||||||
this.$emit('click', e);
|
this.$emit('click', e);
|
||||||
},
|
},
|
||||||
increment(value){
|
|
||||||
this.$emit('change', {type: 'increment', value})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -124,22 +89,9 @@ export default {
|
|||||||
.spell-slot-list-tile {
|
.spell-slot-list-tile {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
}
|
}
|
||||||
.spell-slot-list-tile >>> .v-list__tile {
|
|
||||||
height: 56px;
|
|
||||||
}
|
|
||||||
.v-list__tile__action {
|
.v-list__tile__action {
|
||||||
width: 112px;
|
width: 112px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
|
||||||
.slot-buttons > .v-btn {
|
|
||||||
margin: 0;
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
.buttons {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.buttons > .v-btn {
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
.spell-slot-list-tile.hover {
|
.spell-slot-list-tile.hover {
|
||||||
background: #f5f5f5 !important;
|
background: #f5f5f5 !important;
|
||||||
@@ -156,4 +108,7 @@ export default {
|
|||||||
.theme--dark .max-value {
|
.theme--dark .max-value {
|
||||||
color: rgba(255, 255, 255, 0.54);
|
color: rgba(255, 255, 255, 0.54);
|
||||||
}
|
}
|
||||||
|
.primary--text .v-icon, .primary--text .max-value, .primary--text .current-value, .primary--text .v-list__tile__sub-title {
|
||||||
|
color: #b71c1c
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
v-if="$vuetify.breakpoint.smAndUp"
|
||||||
|
class="layout row split"
|
||||||
|
>
|
||||||
|
<v-list
|
||||||
|
class="left"
|
||||||
|
subheader
|
||||||
|
two-line
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<v-slide-x-transition
|
||||||
|
group
|
||||||
|
leave-absolute
|
||||||
|
>
|
||||||
|
<slot name="left" />
|
||||||
|
</v-slide-x-transition>
|
||||||
|
</v-list>
|
||||||
|
<v-divider
|
||||||
|
class="mx-3"
|
||||||
|
vertical
|
||||||
|
/>
|
||||||
|
<v-list
|
||||||
|
class="right"
|
||||||
|
subheader
|
||||||
|
two-line
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<v-slide-x-transition
|
||||||
|
group
|
||||||
|
leave-absolute
|
||||||
|
>
|
||||||
|
<slot name="right" />
|
||||||
|
</v-slide-x-transition>
|
||||||
|
</v-list>
|
||||||
|
</div>
|
||||||
|
<v-list
|
||||||
|
v-else
|
||||||
|
class="small"
|
||||||
|
subheader
|
||||||
|
two-line
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<v-slide-x-transition
|
||||||
|
group
|
||||||
|
leave-absolute
|
||||||
|
>
|
||||||
|
<slot name="left" />
|
||||||
|
<slot name="right" />
|
||||||
|
</v-slide-x-transition>
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.split{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.left, .right {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
flex-basis: 250px;
|
||||||
|
}
|
||||||
|
.right, .small {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<dialog-base>
|
||||||
|
<template slot="toolbar">
|
||||||
|
<v-toolbar-title>
|
||||||
|
Cast a Spell
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer />
|
||||||
|
<v-input icon="search" />
|
||||||
|
</template>
|
||||||
|
<split-list-layout>
|
||||||
|
<template slot="left">
|
||||||
|
<div
|
||||||
|
key="slot-title"
|
||||||
|
class="title my-3"
|
||||||
|
>
|
||||||
|
Slot
|
||||||
|
</div>
|
||||||
|
<v-list-tile
|
||||||
|
v-if="!(selectedSpell && selectedSpell.level > 0)"
|
||||||
|
key="cantrip-dummy-slot"
|
||||||
|
class="spell-slot-list-tile"
|
||||||
|
:class="{ 'primary--text': selectedSlotId === undefined}"
|
||||||
|
@click="selectedSlotId = undefined"
|
||||||
|
>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title class="title">
|
||||||
|
Cantrip
|
||||||
|
</v-list-tile-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
</v-list-tile>
|
||||||
|
<spell-slot-list-tile
|
||||||
|
v-for="spellSlot in spellSlots"
|
||||||
|
:key="spellSlot._id"
|
||||||
|
:model="spellSlot"
|
||||||
|
:class="{ 'primary--text': selectedSlotId === spellSlot._id }"
|
||||||
|
hide-cast-button
|
||||||
|
@click="selectedSlotId = spellSlot._id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template slot="right">
|
||||||
|
<div
|
||||||
|
key="spell-title"
|
||||||
|
class="title my-3"
|
||||||
|
>
|
||||||
|
Spell
|
||||||
|
</div>
|
||||||
|
<template v-for="spell in computedSpells">
|
||||||
|
<v-subheader
|
||||||
|
v-if="spell.isSubheader"
|
||||||
|
:key="`${spell.level}-header`"
|
||||||
|
class="item"
|
||||||
|
>
|
||||||
|
{{ spell.level === 0 ? 'Cantrips' : `Level ${spell.level}` }}
|
||||||
|
</v-subheader>
|
||||||
|
<spell-list-tile
|
||||||
|
v-else
|
||||||
|
:key="spell._id"
|
||||||
|
hide-handle
|
||||||
|
:class="{ 'primary--text': selectedSpellId === spell._id}"
|
||||||
|
:model="spell"
|
||||||
|
@click="selectedSpellId = spell._id"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</split-list-layout>
|
||||||
|
<template slot="actions">
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
@click="$store.dispatch('popDialogStack')"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
flat
|
||||||
|
:disabled="!canCast"
|
||||||
|
class="primary--text"
|
||||||
|
@click="$store.dispatch('popDialogStack', {
|
||||||
|
spellId: selectedSpellId,
|
||||||
|
slotId: selectedSlotId,
|
||||||
|
})"
|
||||||
|
>
|
||||||
|
Cast
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
</dialog-base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DialogBase from '/imports/ui/dialogStack/DialogBase.vue';
|
||||||
|
import SplitListLayout from '/imports/ui/properties/components/attributes/SplitListLayout.vue';
|
||||||
|
import CreatureProperties from '/imports/api/creature/CreatureProperties.js';
|
||||||
|
import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js';
|
||||||
|
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||||
|
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DialogBase,
|
||||||
|
SplitListLayout,
|
||||||
|
SpellSlotListTile,
|
||||||
|
SpellListTile,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
creatureId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
slotId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
spellId: {
|
||||||
|
type: String,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data(){ return {
|
||||||
|
searchString: undefined,
|
||||||
|
selectedSlotId: this.slotId,
|
||||||
|
selectedSpellId: this.spellId,
|
||||||
|
}},
|
||||||
|
computed: {
|
||||||
|
computedSpells(){
|
||||||
|
return spellsWithSubheaders(this.spells);
|
||||||
|
},
|
||||||
|
canCast(){
|
||||||
|
let spell = this.selectedSpell;
|
||||||
|
let slot = this.selectedSlot;
|
||||||
|
if (!spell) return false;
|
||||||
|
if (spell.level === 0){
|
||||||
|
return this.selectedSlotId === undefined;
|
||||||
|
} else if (!slot) {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return slot.spellSlotLevelValue >= spell.level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
selectedSpell(spell){
|
||||||
|
if (!spell) return;
|
||||||
|
if(spell.level === 0){
|
||||||
|
this.selectedSlotId = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedSlot(slot){
|
||||||
|
if (!slot) return;
|
||||||
|
if (!this.selectedSpell) return;
|
||||||
|
if(slot.spellSlotLevelValue > 0 && this.selectedSpell.level === 0){
|
||||||
|
this.selectedSpellId = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
spells(){
|
||||||
|
let slotLevel = this.selectedSlot && this.selectedSlot.spellSlotLevelValue || 0;
|
||||||
|
return CreatureProperties.find({
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
prepared: true,
|
||||||
|
level: {$lte: slotLevel},
|
||||||
|
}, {
|
||||||
|
sort: {order: 1}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
spellSlots(){
|
||||||
|
let filter = {
|
||||||
|
'ancestors.id': this.creatureId,
|
||||||
|
type: 'attribute',
|
||||||
|
attributeType: 'spellSlot',
|
||||||
|
removed: {$ne: true},
|
||||||
|
inactive: {$ne: true},
|
||||||
|
currentValue: {$gte: 1},
|
||||||
|
};
|
||||||
|
if (this.selectedSpell){
|
||||||
|
filter.spellSlotLevelValue = {$gte: this.selectedSpell.level};
|
||||||
|
}
|
||||||
|
return CreatureProperties.find(filter, {
|
||||||
|
sort: {order: 1},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectedSlot(){
|
||||||
|
return CreatureProperties.findOne(this.selectedSlotId);
|
||||||
|
},
|
||||||
|
selectedSpell(){
|
||||||
|
return CreatureProperties.findOne(this.selectedSpellId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.v-list {
|
||||||
|
flex-basis: 200px;
|
||||||
|
}
|
||||||
|
.v-list.spells {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -40,24 +40,7 @@
|
|||||||
import draggable from 'vuedraggable';
|
import draggable from 'vuedraggable';
|
||||||
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
|
import SpellListTile from '/imports/ui/properties/components/spells/SpellListTile.vue';
|
||||||
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
import { organizeDoc } from '/imports/api/parenting/organizeMethods.js';
|
||||||
|
import spellsWithSubheaders from '/imports/ui/properties/components/spells/spellsWithSubheaders.js';
|
||||||
function spellsWithSubheaders(spells = []){
|
|
||||||
let result = [];
|
|
||||||
let lastSpell = undefined;
|
|
||||||
let sortedSpells = [...spells].sort((a, b) => a.level - b.level)
|
|
||||||
sortedSpells.forEach(spell => {
|
|
||||||
if (spell.isSubheader) return;
|
|
||||||
if (!lastSpell || spell.level > lastSpell.level){
|
|
||||||
result.push({
|
|
||||||
isSubheader: true,
|
|
||||||
level: spell.level,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
result.push(spell);
|
|
||||||
lastSpell = spell;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
@change="setPrepared"
|
@change="setPrepared"
|
||||||
/>
|
/>
|
||||||
<v-icon
|
<v-icon
|
||||||
v-else
|
v-else-if="!hideHandle"
|
||||||
style="height: 100%; width: 40px; cursor: move;"
|
style="height: 100%; width: 40px; cursor: move;"
|
||||||
class="handle"
|
class="handle"
|
||||||
>
|
>
|
||||||
@@ -45,6 +45,7 @@ export default {
|
|||||||
mixins: [treeNodeViewMixin],
|
mixins: [treeNodeViewMixin],
|
||||||
props: {
|
props: {
|
||||||
preparingSpells: Boolean,
|
preparingSpells: Boolean,
|
||||||
|
hideHandle: Boolean,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasClickListener(){
|
hasClickListener(){
|
||||||
@@ -82,4 +83,7 @@ export default {
|
|||||||
.spell {
|
.spell {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
.primary--text .v-icon, .primary--text .v-list__tile__sub-title {
|
||||||
|
color: #b71c1c
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
export default function spellsWithSubheaders(spells = []){
|
||||||
|
let result = [];
|
||||||
|
let lastSpell = undefined;
|
||||||
|
let sortedSpells = [...spells].sort((a, b) => a.level - b.level)
|
||||||
|
sortedSpells.forEach(spell => {
|
||||||
|
if (spell.isSubheader) return;
|
||||||
|
if (!lastSpell || spell.level > lastSpell.level){
|
||||||
|
result.push({
|
||||||
|
isSubheader: true,
|
||||||
|
level: spell.level,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.push(spell);
|
||||||
|
lastSpell = spell;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user