Overhaul of character action components, actions now consume resources
This commit is contained in:
@@ -343,7 +343,45 @@ const adjustQuantity = new ValidatedMethod({
|
|||||||
let currentProperty = CreatureProperties.findOne(_id);
|
let currentProperty = CreatureProperties.findOne(_id);
|
||||||
// Check permissions
|
// Check permissions
|
||||||
assertPropertyEditPermission(currentProperty, this.userId);
|
assertPropertyEditPermission(currentProperty, this.userId);
|
||||||
adjustQuantityWork({property: currentProperty, operation, value})
|
adjustQuantityWork({property: currentProperty, operation, value});
|
||||||
|
recomputeCreatures(currentProperty);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectAmmoItem = new ValidatedMethod({
|
||||||
|
name: 'creatureProperties.selectAmmoItem',
|
||||||
|
validate: new SimpleSchema({
|
||||||
|
actionId: SimpleSchema.RegEx.Id,
|
||||||
|
itemId: SimpleSchema.RegEx.Id,
|
||||||
|
itemConsumedIndex: Number,
|
||||||
|
}).validator(),
|
||||||
|
mixins: [RateLimiterMixin],
|
||||||
|
rateLimit: {
|
||||||
|
numRequests: 5,
|
||||||
|
timeInterval: 5000,
|
||||||
|
},
|
||||||
|
run({actionId, itemId, itemConsumedIndex}) {
|
||||||
|
let action = CreatureProperties.findOne(actionId);
|
||||||
|
// Check permissions
|
||||||
|
assertPropertyEditPermission(action, this.userId);
|
||||||
|
// Check that this index has a document to edit
|
||||||
|
let itemConsumed = action.resources.itemsConsumed[itemConsumedIndex];
|
||||||
|
if (!itemConsumed){
|
||||||
|
throw new Meteor.Error('Resouce not found',
|
||||||
|
'Could not set ammo, because the ammo document was not found');
|
||||||
|
}
|
||||||
|
let itemToLink = CreatureProperties.findOne(itemId);
|
||||||
|
if (!itemToLink){
|
||||||
|
throw new Meteor.Error('Item not found',
|
||||||
|
'Could not set ammo: the item was not found');
|
||||||
|
}
|
||||||
|
let path = `resources.itemsConsumed.${itemConsumedIndex}.itemId`;
|
||||||
|
CreatureProperties.update(actionId, {
|
||||||
|
$set: {[path]: itemId}
|
||||||
|
}, {
|
||||||
|
selector: action,
|
||||||
|
});
|
||||||
|
recomputeCreatures(action);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -416,6 +454,7 @@ export {
|
|||||||
updateProperty,
|
updateProperty,
|
||||||
damageProperty,
|
damageProperty,
|
||||||
adjustQuantity,
|
adjustQuantity,
|
||||||
|
selectAmmoItem,
|
||||||
pushToProperty,
|
pushToProperty,
|
||||||
pullFromProperty,
|
pullFromProperty,
|
||||||
softRemoveProperty,
|
softRemoveProperty,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export default class ComputationMemo {
|
|||||||
this.classes = {};
|
this.classes = {};
|
||||||
this.togglesById = {};
|
this.togglesById = {};
|
||||||
this.toggleIds = new Set();
|
this.toggleIds = new Set();
|
||||||
|
// Equipped items that might be used as ammo
|
||||||
|
this.equipmentById = {};
|
||||||
// Properties that have calculations, but don't impact other properties
|
// Properties that have calculations, but don't impact other properties
|
||||||
this.endStepPropsById = {};
|
this.endStepPropsById = {};
|
||||||
// First note all the ids of all the toggles
|
// First note all the ids of all the toggles
|
||||||
@@ -40,6 +42,10 @@ export default class ComputationMemo {
|
|||||||
) {
|
) {
|
||||||
// Add all the stats
|
// Add all the stats
|
||||||
this.addStat(prop);
|
this.addStat(prop);
|
||||||
|
} else if (
|
||||||
|
prop.type === 'item'
|
||||||
|
) {
|
||||||
|
this.addEquipment(prop);
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -185,6 +191,10 @@ export default class ComputationMemo {
|
|||||||
});
|
});
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
addEquipment(prop){
|
||||||
|
prop = this.registerProperty(prop);
|
||||||
|
this.equipmentById[prop._id] = prop;
|
||||||
|
}
|
||||||
addEndStepProp(prop){
|
addEndStepProp(prop){
|
||||||
prop = this.registerProperty(prop);
|
prop = this.registerProperty(prop);
|
||||||
this.endStepPropsById[prop._id] = prop;
|
this.endStepPropsById[prop._id] = prop;
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ function computeAction(prop, memo){
|
|||||||
if (attConsumed.variableName){
|
if (attConsumed.variableName){
|
||||||
let stat = memo.statsByVariableName[attConsumed.variableName];
|
let stat = memo.statsByVariableName[attConsumed.variableName];
|
||||||
prop.resources.attributesConsumed[i].statId = stat && stat._id;
|
prop.resources.attributesConsumed[i].statId = stat && stat._id;
|
||||||
|
prop.resources.attributesConsumed[i].statName = stat && stat.name;
|
||||||
let available = stat && stat.currentValue || 0;
|
let available = stat && stat.currentValue || 0;
|
||||||
prop.resources.attributesConsumed[i].available = available;
|
prop.resources.attributesConsumed[i].available = available;
|
||||||
if (available < attConsumed.quantity){
|
if (available < attConsumed.quantity){
|
||||||
@@ -45,7 +46,22 @@ function computeAction(prop, memo){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Items consumed
|
// Items consumed
|
||||||
// TODO
|
prop.resources.itemsConsumed.forEach((itemConsumed, i) => {
|
||||||
|
let item = itemConsumed.itemId && memo.equipmentById[itemConsumed.itemId];
|
||||||
|
prop.resources.itemsConsumed[i].itemId = item && item._id;
|
||||||
|
let available = item && item.quantity || 0;
|
||||||
|
prop.resources.itemsConsumed[i].available = available;
|
||||||
|
let name = item && item.name;
|
||||||
|
if (item && item.quantity !== 1 && item.plural){
|
||||||
|
name = item.plural;
|
||||||
|
}
|
||||||
|
prop.resources.itemsConsumed[i].itemName = name;
|
||||||
|
prop.resources.itemsConsumed[i].itemIcon = item && item.icon;
|
||||||
|
prop.resources.itemsConsumed[i].itemColor = item && item.color;
|
||||||
|
if (!item || available < itemConsumed.quantity){
|
||||||
|
prop.insufficientResources = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeAttack(prop, memo){
|
function computeAttack(prop, memo){
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const calculationPropertyTypes = [
|
|||||||
'proficiency',
|
'proficiency',
|
||||||
'classLevel',
|
'classLevel',
|
||||||
'toggle',
|
'toggle',
|
||||||
|
'item',
|
||||||
// End step types
|
// End step types
|
||||||
'action',
|
'action',
|
||||||
'attack',
|
'attack',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
import SimpleSchema from 'simpl-schema';
|
||||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||||
|
import { storedIconsSchema } from '/imports/api/icons/Icons.js'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Actions are things a character can do
|
* Actions are things a character can do
|
||||||
@@ -133,6 +134,19 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
|||||||
// This appears both in the computed and uncomputed schema because it can be
|
// This appears both in the computed and uncomputed schema because it can be
|
||||||
// set by both a computation or a form
|
// set by both a computation or a form
|
||||||
'resources.itemsConsumed.$.itemId': {
|
'resources.itemsConsumed.$.itemId': {
|
||||||
|
type: String,
|
||||||
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'resources.itemsConsumed.$.itemName': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'resources.itemsConsumed.$.itemIcon': {
|
||||||
|
type: storedIconsSchema,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'resources.itemsConsumed.$.itemColor': {
|
||||||
type: String,
|
type: String,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
@@ -147,6 +161,12 @@ const ComputedOnlyActionSchema = new SimpleSchema({
|
|||||||
regEx: SimpleSchema.RegEx.Id,
|
regEx: SimpleSchema.RegEx.Id,
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
'resources.attributesConsumed.$.statName': {
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// True if the uses left is zero, or any item or attribute consumed is
|
||||||
|
// insufficient
|
||||||
insufficientResources: {
|
insufficientResources: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
|||||||
@@ -34,8 +34,7 @@
|
|||||||
drag_handle
|
drag_handle
|
||||||
</v-icon>
|
</v-icon>
|
||||||
<!--{{node && node.order}}-->
|
<!--{{node && node.order}}-->
|
||||||
<component
|
<tree-node-view
|
||||||
:is="treeNodeView"
|
|
||||||
:model="node"
|
:model="node"
|
||||||
:selected="selected"
|
:selected="selected"
|
||||||
/>
|
/>
|
||||||
@@ -80,13 +79,13 @@
|
|||||||
**/
|
**/
|
||||||
import { canBeParent } from '/imports/api/parenting/parenting.js';
|
import { canBeParent } from '/imports/api/parenting/parenting.js';
|
||||||
import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
|
||||||
import treeNodeViewIndex from '/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js';
|
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TreeNode',
|
name: 'TreeNode',
|
||||||
components: {
|
components: {
|
||||||
...treeNodeViewIndex
|
TreeNodeView,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
node: Object,
|
node: Object,
|
||||||
group: String,
|
group: String,
|
||||||
@@ -100,10 +99,6 @@
|
|||||||
expanded: false,
|
expanded: false,
|
||||||
}},
|
}},
|
||||||
computed: {
|
computed: {
|
||||||
treeNodeView(){
|
|
||||||
let type = this.node.type;
|
|
||||||
return treeNodeViewIndex[type] || treeNodeViewIndex.default;
|
|
||||||
},
|
|
||||||
hasChildren(){
|
hasChildren(){
|
||||||
return this.children && this.children.length || this.lazy && !this.expanded;
|
return this.children && this.children.length || this.lazy && !this.expanded;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-btn
|
<v-btn
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:disabled="loading"
|
|
||||||
outline
|
outline
|
||||||
style="width: 160px;"
|
style="width: 160px;"
|
||||||
@click="rest"
|
@click="rest"
|
||||||
|
|||||||
@@ -258,25 +258,16 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="attacks.length"
|
v-for="attack in attacks"
|
||||||
class="actions"
|
:key="attack._id"
|
||||||
|
class="attacks"
|
||||||
>
|
>
|
||||||
<v-card>
|
<action-card
|
||||||
<v-list
|
attack
|
||||||
two-line
|
:model="attack"
|
||||||
subheader
|
:data-id="attack._id"
|
||||||
>
|
@click="clickProperty({_id: attack._id})"
|
||||||
<v-subheader>Attacks</v-subheader>
|
/>
|
||||||
<action-list-tile
|
|
||||||
v-for="attack in attacks"
|
|
||||||
:key="attack._id"
|
|
||||||
attack
|
|
||||||
:model="attack"
|
|
||||||
:data-id="attack._id"
|
|
||||||
@click="clickProperty({_id: attack._id})"
|
|
||||||
/>
|
|
||||||
</v-list>
|
|
||||||
</v-card>
|
|
||||||
</div>
|
</div>
|
||||||
</column-layout>
|
</column-layout>
|
||||||
</div>
|
</div>
|
||||||
@@ -294,7 +285,6 @@
|
|||||||
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
import SkillListTile from '/imports/ui/properties/components/skills/SkillListTile.vue';
|
||||||
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
import ResourceCard from '/imports/ui/properties/components/attributes/ResourceCard.vue';
|
||||||
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
import SpellSlotListTile from '/imports/ui/properties/components/attributes/SpellSlotListTile.vue';
|
||||||
import ActionListTile from '/imports/ui/properties/components/actions/ActionListTile.vue';
|
|
||||||
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';
|
||||||
@@ -337,7 +327,6 @@
|
|||||||
SkillListTile,
|
SkillListTile,
|
||||||
ResourceCard,
|
ResourceCard,
|
||||||
SpellSlotListTile,
|
SpellSlotListTile,
|
||||||
ActionListTile,
|
|
||||||
ActionCard,
|
ActionCard,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@@ -390,14 +379,7 @@
|
|||||||
return getSkillOfType(this.creature, 'language');
|
return getSkillOfType(this.creature, 'language');
|
||||||
},
|
},
|
||||||
actions(){
|
actions(){
|
||||||
let props = getProperties(this.creature, {type: 'action'}).map(action => {
|
return getProperties(this.creature, {type: 'action'});
|
||||||
action.children = getActiveProperties({
|
|
||||||
ancestorId: action._id,
|
|
||||||
options: {sort: {order: 1}},
|
|
||||||
});
|
|
||||||
return action;
|
|
||||||
});
|
|
||||||
return props;
|
|
||||||
},
|
},
|
||||||
attacks(){
|
attacks(){
|
||||||
let props = getProperties(this.creature, {type: 'attack'}).map(attack => {
|
let props = getProperties(this.creature, {type: 'attack'}).map(attack => {
|
||||||
|
|||||||
@@ -160,7 +160,6 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
push({path, value, ack}){
|
push({path, value, ack}){
|
||||||
console.log({path, value, ack});
|
|
||||||
pushToProperty.call({_id: this._id, path, value}, (error) =>{
|
pushToProperty.call({_id: this._id, path, value}, (error) =>{
|
||||||
if (error) console.warn(error);
|
if (error) console.warn(error);
|
||||||
ack && ack(error && error.reason || error);
|
ack && ack(error && error.reason || error);
|
||||||
|
|||||||
@@ -1,58 +1,106 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-card
|
<v-card
|
||||||
class="action"
|
ref="card"
|
||||||
@click="$emit('click')"
|
class="action-card"
|
||||||
|
:class="cardClasses"
|
||||||
|
:elevation="hovering ? 8 : undefined"
|
||||||
>
|
>
|
||||||
<v-card-title
|
<div class="layout row align-center px-3">
|
||||||
primary-title
|
|
||||||
class="layout row pa-2"
|
|
||||||
>
|
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<v-btn
|
<v-btn
|
||||||
flat
|
flat
|
||||||
icon
|
icon
|
||||||
class="headline"
|
outline
|
||||||
|
style="margin-left: -4px; font-size: 18px;"
|
||||||
|
color="primary"
|
||||||
|
:loading="doActionLoading"
|
||||||
:disabled="model.insufficientResources"
|
:disabled="model.insufficientResources"
|
||||||
@click.stop="doAction"
|
@click.stop="doAction"
|
||||||
>
|
>
|
||||||
<template v-if="rollBonus">
|
<template v-if="attack && !rollBonusTooLong">
|
||||||
{{ rollBonus }}
|
{{ rollBonus }}
|
||||||
</template>
|
</template>
|
||||||
<v-icon v-else>
|
<v-icon v-else>
|
||||||
$vuetify.icons.action
|
{{ actionTypeIcon }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-header flex">
|
<div
|
||||||
<div class="action-title">
|
class="action-header flex layout column justify-center pl-1"
|
||||||
|
style="height: 72px; cursor: pointer;"
|
||||||
|
@mouseover="hovering = true"
|
||||||
|
@mouseleave="hovering = false"
|
||||||
|
@click="$emit('click')"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="action-title my-1"
|
||||||
|
>
|
||||||
{{ model.name }}
|
{{ model.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="action-type">
|
<div class="action-sub-title layout row align-center">
|
||||||
action type text
|
<div class="flex">
|
||||||
|
{{ model.actionType }}
|
||||||
|
</div>
|
||||||
|
<div v-if="model.uses">
|
||||||
|
{{ usesLeft }} uses
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-uses">
|
</div>
|
||||||
{{ usesLeft }}/{{ totalUses }}
|
<div class="px-3 pb-3">
|
||||||
</div>
|
<attribute-consumed-view
|
||||||
</v-card-title>
|
v-for="attributeConsumed in model.resources.attributesConsumed"
|
||||||
<v-card-text
|
:key="attributeConsumed._id"
|
||||||
v-if="childText"
|
class="action-child"
|
||||||
class="action-details"
|
:model="attributeConsumed"
|
||||||
v-html="childText"
|
/>
|
||||||
/>
|
<item-consumed-view
|
||||||
|
v-for="itemConsumed in model.resources.itemsConsumed"
|
||||||
|
:key="itemConsumed._id"
|
||||||
|
class="action-child"
|
||||||
|
:model="itemConsumed"
|
||||||
|
:action="model"
|
||||||
|
/>
|
||||||
|
<v-divider
|
||||||
|
v-if="
|
||||||
|
model.resources.attributesConsumed.length ||
|
||||||
|
model.resources.itemsConsumed.length
|
||||||
|
"
|
||||||
|
class="my-2"
|
||||||
|
/>
|
||||||
|
<tree-node-view
|
||||||
|
v-for="child in children"
|
||||||
|
:key="child._id"
|
||||||
|
class="action-child"
|
||||||
|
:model="child"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
|
||||||
import doAction from '/imports/api/creature/actions/doAction.js';
|
import doAction from '/imports/api/creature/actions/doAction.js';
|
||||||
|
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||||
|
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||||
|
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||||
|
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
TreeNodeView,
|
||||||
|
AttributeConsumedView,
|
||||||
|
ItemConsumedView,
|
||||||
|
},
|
||||||
inject: {
|
inject: {
|
||||||
context: {
|
context: {
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
|
theme: {
|
||||||
|
default: {
|
||||||
|
isDark: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
model: {
|
model: {
|
||||||
@@ -63,48 +111,118 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data(){return {
|
||||||
|
activated: undefined,
|
||||||
|
doActionLoading: false,
|
||||||
|
hovering: false,
|
||||||
|
}},
|
||||||
computed: {
|
computed: {
|
||||||
hasClickListener(){
|
hasClickListener(){
|
||||||
return this.$listeners && this.$listeners.click
|
return this.$listeners && this.$listeners.click
|
||||||
},
|
},
|
||||||
rollBonus(){
|
rollBonus(){
|
||||||
|
if (!this.attack) return;
|
||||||
return numberToSignedString(this.model.rollBonusResult);
|
return numberToSignedString(this.model.rollBonusResult);
|
||||||
},
|
},
|
||||||
childText(){
|
rollBonusTooLong(){
|
||||||
let scope = this.context.creature && this.context.creature.variables;
|
return this.rollBonus && this.rollBonus.length > 3;
|
||||||
if (!this.model.children || !this.model.children.length) return;
|
|
||||||
let textArray = [];
|
|
||||||
this.model.children.forEach(child => {
|
|
||||||
if (child.type === 'damage'){
|
|
||||||
let { result } = evaluateString(child.amount, scope);
|
|
||||||
textArray.push(`${result} ${child.damageType}`);
|
|
||||||
} else if (child.type === 'savingThrow'){
|
|
||||||
textArray.push(`DC ${child.dcResult} ${child.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return textArray.join(' ');
|
|
||||||
},
|
},
|
||||||
totalUses(){
|
totalUses(){
|
||||||
return Math.max(this.model.usesResult, 0);
|
return Math.max(this.model.usesResult, 0);
|
||||||
},
|
},
|
||||||
usesLeft(){
|
usesLeft(){
|
||||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
||||||
|
},
|
||||||
|
cardClasses() {
|
||||||
|
return {
|
||||||
|
'theme--dark': this.theme.isDark,
|
||||||
|
'theme--light': !this.theme.isDark,
|
||||||
|
'muted-text': this.model.insufficientResources,
|
||||||
|
'shrink': this.activated,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actionTypeIcon() {
|
||||||
|
return `$vuetify.icons.${this.model.actionType}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
meteor: {
|
||||||
|
children(){
|
||||||
|
return getActiveProperties({
|
||||||
|
ancestorId: this.model._id,
|
||||||
|
filter: {'parent.id': this.model._id},
|
||||||
|
options: {sort: {order: 1}},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
click(e){
|
click(e){
|
||||||
this.$emit('click', e);
|
this.$emit('click', e);
|
||||||
},
|
},
|
||||||
doAction(){
|
doAction(){
|
||||||
|
this.doActionLoading = true;
|
||||||
|
this.shwing();
|
||||||
doAction.call({actionId: this.model._id}, error => {
|
doAction.call({actionId: this.model._id}, error => {
|
||||||
|
this.doActionLoading = false;
|
||||||
if (error){
|
if (error){
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
shwing(){
|
||||||
|
this.activated = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.activated = undefined;
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
.action-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
position: relative;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: .3s cubic-bezier(.25,.8,.5,1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.action-sub-title {
|
||||||
|
color: #9e9e9e;
|
||||||
|
flex-grow: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 12px;
|
||||||
|
height: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: .3s cubic-bezier(.25,.8,.5,1);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.action-child {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
.theme--light.muted-text {
|
||||||
|
color: rgba(0,0,0,.3) !important;
|
||||||
|
}
|
||||||
|
.theme--dark.muted-text {
|
||||||
|
color: hsla(0,0%,100%,.3) !important;
|
||||||
|
}
|
||||||
|
.action-card {
|
||||||
|
transition: transform 0.15s cubic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.action-card.theme--light.muted-text .v-icon {
|
||||||
|
color: rgba(0,0,0,.3) !important;
|
||||||
|
}
|
||||||
|
.action-card.theme--dark.muted-text .v-icon {
|
||||||
|
color: hsla(0,0%,100%,.3) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
<template lang="html">
|
|
||||||
<v-list-tile
|
|
||||||
class="ability-list-tile"
|
|
||||||
v-on="hasClickListener ? {click} : {}"
|
|
||||||
>
|
|
||||||
<v-list-tile-action
|
|
||||||
v-if="attack"
|
|
||||||
>
|
|
||||||
<v-btn
|
|
||||||
flat
|
|
||||||
icon
|
|
||||||
class="headline"
|
|
||||||
:disabled="model.insufficientResources"
|
|
||||||
@click.stop="doAction"
|
|
||||||
>
|
|
||||||
{{ rollBonus }}
|
|
||||||
</v-btn>
|
|
||||||
</v-list-tile-action>
|
|
||||||
<v-list-tile-content>
|
|
||||||
<v-list-tile-title>
|
|
||||||
{{ model.name }}
|
|
||||||
</v-list-tile-title>
|
|
||||||
<v-list-tile-sub-title
|
|
||||||
v-if="childText"
|
|
||||||
v-html="childText"
|
|
||||||
/>
|
|
||||||
</v-list-tile-content>
|
|
||||||
<v-list-tile-action v-if="model.usesResult">
|
|
||||||
<v-list-tile-action-text>
|
|
||||||
{{ usesLeft }}/{{ totalUses }}
|
|
||||||
</v-list-tile-action-text>
|
|
||||||
</v-list-tile-action>
|
|
||||||
</v-list-tile>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
|
||||||
import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js';
|
|
||||||
import doAction from '/imports/api/creature/actions/doAction.js';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
inject: {
|
|
||||||
context: {
|
|
||||||
default: {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
model: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
attack: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
hasClickListener(){
|
|
||||||
return this.$listeners && this.$listeners.click
|
|
||||||
},
|
|
||||||
rollBonus(){
|
|
||||||
return numberToSignedString(this.model.rollBonusResult);
|
|
||||||
},
|
|
||||||
childText(){
|
|
||||||
let scope = this.context.creature && this.context.creature.variables;
|
|
||||||
if (!this.model.children || !this.model.children.length) return;
|
|
||||||
let textArray = [];
|
|
||||||
this.model.children.forEach(child => {
|
|
||||||
if (child.type === 'damage'){
|
|
||||||
let { result } = evaluateString(child.amount, scope);
|
|
||||||
textArray.push(`${result} ${child.damageType}`);
|
|
||||||
} else if (child.type === 'savingThrow'){
|
|
||||||
textArray.push(`DC ${child.dcResult} ${child.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return textArray.join(' ');
|
|
||||||
},
|
|
||||||
totalUses(){
|
|
||||||
return Math.max(this.model.usesResult, 0);
|
|
||||||
},
|
|
||||||
usesLeft(){
|
|
||||||
return Math.max(this.model.usesResult - this.model.usesUsed, 0);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
click(e){
|
|
||||||
this.$emit('click', e);
|
|
||||||
},
|
|
||||||
doAction(){
|
|
||||||
doAction.call({actionId: this.model._id}, error => {
|
|
||||||
if (error){
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="css" scoped>
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
class="layout row align-center justify-start"
|
||||||
|
:class="insufficient && 'error--text'"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mr-2"
|
||||||
|
style="width: 24px; text-align: center;"
|
||||||
|
>
|
||||||
|
{{ model.quantity }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-no-wrap text-truncate"
|
||||||
|
>
|
||||||
|
{{ model.statName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
insufficient(){
|
||||||
|
return this.model.quantity > this.model.available;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
'theme--dark': theme.isDark,
|
||||||
|
'theme--light': !theme.isDark,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<v-menu
|
||||||
|
transition="slide-y-transition"
|
||||||
|
lazy
|
||||||
|
>
|
||||||
|
<template #activator="{ on }">
|
||||||
|
<div
|
||||||
|
class="layout row align-center justify-start"
|
||||||
|
style="height: 100%;"
|
||||||
|
:class="{
|
||||||
|
'error--text': insufficient,
|
||||||
|
'clickable': context.editPermission,
|
||||||
|
}"
|
||||||
|
v-on="on"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mr-2"
|
||||||
|
style="width: 24px; text-align: center;"
|
||||||
|
>
|
||||||
|
<template v-if="model.quantity === 1">
|
||||||
|
{{ model.available }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="model.quantity !== 0">
|
||||||
|
{{ model.available }} / {{ model.quantity }}
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<svg-icon
|
||||||
|
v-if="model.itemIcon"
|
||||||
|
:shape="model.itemIcon.shape"
|
||||||
|
:color="model.itemColor"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="text-no-wrap text-truncate flex"
|
||||||
|
>
|
||||||
|
<template v-if="model.itemId">
|
||||||
|
{{ model.itemName }}
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="error--text"
|
||||||
|
>
|
||||||
|
Select ammo
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<v-icon v-if="context.editPermission">
|
||||||
|
arrow_drop_down
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<select-item-to-consume
|
||||||
|
:action="action"
|
||||||
|
:item-consumed="model"
|
||||||
|
/>
|
||||||
|
</v-menu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SelectItemToConsume from '/imports/ui/properties/components/actions/SelectItemToConsume.vue';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SelectItemToConsume,
|
||||||
|
},
|
||||||
|
inject: {
|
||||||
|
context: {
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
theme: {
|
||||||
|
default: {
|
||||||
|
isDark: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
insufficient(){
|
||||||
|
return this.model.quantity > this.model.available;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.theme--light .clickable:hover {
|
||||||
|
background: rgba(0,0,0,.04);
|
||||||
|
}
|
||||||
|
.theme--dark .clickable:hover {
|
||||||
|
background: hsla(0,0%,100%,.08);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-list v-if="items.length">
|
||||||
|
<v-list-tile
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item._id"
|
||||||
|
@click="selectItem(item._id)"
|
||||||
|
>
|
||||||
|
<item-tree-node
|
||||||
|
:model="item"
|
||||||
|
:selected="itemConsumed.itemId === item._id"
|
||||||
|
/>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
<v-card v-else>
|
||||||
|
<v-card-text>
|
||||||
|
No equipped items found with the tag "{{ itemConsumed.tag }}"
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ItemTreeNode from '/imports/ui/properties/treeNodeViews/ItemTreeNode.vue';
|
||||||
|
import getActiveProperties from '/imports/api/creature/getActiveProperties.js';
|
||||||
|
import { selectAmmoItem } from '/imports/api/creature/CreatureProperties.js';
|
||||||
|
import { findIndex } from 'lodash';
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ItemTreeNode
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
action: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
itemConsumed: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
items(){
|
||||||
|
return getActiveProperties({
|
||||||
|
ancestorId: this.action.ancestors[0].id,
|
||||||
|
filter: {
|
||||||
|
tags: this.itemConsumed.tag,
|
||||||
|
equipped: true,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
fields: {equipped: false},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
selectItem(itemId){
|
||||||
|
let itemConsumedIndex = findIndex(
|
||||||
|
this.action.resources.itemsConsumed,
|
||||||
|
item => item._id === this.itemConsumed._id
|
||||||
|
);
|
||||||
|
selectAmmoItem.call({
|
||||||
|
actionId: this.action._id,
|
||||||
|
itemId,
|
||||||
|
itemConsumedIndex
|
||||||
|
}, error => {
|
||||||
|
if (error) console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -83,7 +83,6 @@
|
|||||||
this.addResourceLoading = false;
|
this.addResourceLoading = false;
|
||||||
},
|
},
|
||||||
addAttributesConsumed(){
|
addAttributesConsumed(){
|
||||||
console.log(AttributeConsumedSchema.clean({}));
|
|
||||||
this.addResourceLoading = true;
|
this.addResourceLoading = true;
|
||||||
this.$emit('push', {
|
this.$emit('push', {
|
||||||
path: ['attributesConsumed'],
|
path: ['attributesConsumed'],
|
||||||
|
|||||||
30
app/imports/ui/properties/treeNodeViews/TreeNodeView.vue
Normal file
30
app/imports/ui/properties/treeNodeViews/TreeNodeView.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<component
|
||||||
|
:is="treeNodeView"
|
||||||
|
:model="model"
|
||||||
|
:selected="selected"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import treeNodeViewIndex from '/imports/ui/properties/treeNodeViews/treeNodeViewIndex.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
...treeNodeViewIndex
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selected: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
treeNodeView(){
|
||||||
|
let type = this.model.type;
|
||||||
|
return treeNodeViewIndex[type] || treeNodeViewIndex.default;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user