Overhauled damage multipliers UX
Form and viewer revamp custom damage types Variables: `bludgeoning.resistance`
This commit is contained in:
@@ -133,6 +133,7 @@ let CreatureSchema = new SimpleSchema({
|
||||
'computeErrors.$.details' : {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
|
||||
// Tabletop
|
||||
|
||||
@@ -218,7 +218,7 @@ function linkDamageMultiplier(dependencyGraph, prop){
|
||||
prop.damageTypes.forEach(damageType => {
|
||||
// Remove all non-letter characters from the damage name
|
||||
const damageName = damageType.replace(/[^a-z]/gi, '')
|
||||
dependencyGraph.addLink(`${damageName}Multiplier`, prop._id, prop.type);
|
||||
dependencyGraph.addLink(damageName, prop._id, prop.type);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -242,7 +242,7 @@ function linkSkill(dependencyGraph, prop){
|
||||
}
|
||||
// Skills depend on the creature's proficiencyBonus
|
||||
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
||||
|
||||
|
||||
// Depends on base value
|
||||
dependOnCalc({dependencyGraph, prop, key: 'baseValue'});
|
||||
}
|
||||
|
||||
@@ -54,6 +54,21 @@ function combineAggregations(computation, node){
|
||||
|
||||
function computeVariableProp(computation, node, prop){
|
||||
if (!prop) return;
|
||||
|
||||
// Combine damage multipliers in all props so that they can't be overridden
|
||||
if (node.data.immunity){
|
||||
prop.immunity = node.data.immunity;
|
||||
prop.immunities = node.data.immunities;
|
||||
}
|
||||
if (node.data.resistance){
|
||||
prop.resistance = node.data.resistance;
|
||||
prop.resistances = node.data.resistances;
|
||||
}
|
||||
if (node.data.vulnerability){
|
||||
prop.vulnerability = node.data.vulnerability;
|
||||
prop.vulnerabilities = node.data.vulnerabilities;
|
||||
}
|
||||
|
||||
if (prop.type === 'attribute'){
|
||||
computeVariableAsAttribute(computation, node, prop);
|
||||
} else if (prop.type === 'skill'){
|
||||
@@ -73,21 +88,16 @@ function combineMultiplierAggregator(node){
|
||||
if (!aggregator) return;
|
||||
|
||||
// Combine
|
||||
let value;
|
||||
if (aggregator.immunityCount){
|
||||
value = 0;
|
||||
} else if (
|
||||
aggregator.resistanceCount &&
|
||||
!aggregator.vulnerabilityCount
|
||||
){
|
||||
value = 0.5;
|
||||
} else if (
|
||||
!aggregator.resistanceCount &&
|
||||
aggregator.vulnerabilityCount
|
||||
){
|
||||
value = 2;
|
||||
} else {
|
||||
value = 1;
|
||||
if (aggregator.immunities?.length){
|
||||
node.data.immunity = true;
|
||||
node.data.immunities = aggregator.immunities;
|
||||
}
|
||||
if (aggregator.resistances?.length){
|
||||
node.data.resistance = true;
|
||||
node.data.resistances = aggregator.resistances;
|
||||
}
|
||||
if (aggregator.vulnerabilities?.length){
|
||||
node.data.vulnerability = true;
|
||||
node.data.vulnerabilities = aggregator.vulnerabilities;
|
||||
}
|
||||
node.data.damageMultiplyValue = value;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
import { pick } from 'lodash';
|
||||
|
||||
export default function aggregateDamageMultipliers({node, linkedNode, link}){
|
||||
if (link.data !== 'damageMultiplier') return;
|
||||
const multiplierValue = linkedNode.data.value;
|
||||
if (multiplierValue === undefined) return;
|
||||
|
||||
// Store an aggregator, its presence indicates damage multipliers target this
|
||||
// variable
|
||||
if (!node.data.multiplierAggregator) node.data.multiplierAggregator = {
|
||||
immunityCount: 0,
|
||||
resistanceCount: 0,
|
||||
vulnerabilityCount: 0,
|
||||
immunities: [],
|
||||
resistances: [],
|
||||
vulnerabilities: [],
|
||||
}
|
||||
// Store a short reference to the aggregator
|
||||
const aggregator = node.data.multiplierAggregator;
|
||||
// Sum the counts of each type of multiplier
|
||||
|
||||
// Make a stripped down copy of the multiplier to store in the aggregator
|
||||
const keysToStore = ['_id', 'name'];
|
||||
if (linkedNode.data.excludeTags?.length){
|
||||
keysToStore.push('excludeTags');
|
||||
}
|
||||
if (linkedNode.data.includeTags?.length){
|
||||
keysToStore.push('includeTags');
|
||||
}
|
||||
const storedMultiplier = pick(linkedNode.data, keysToStore);
|
||||
|
||||
// Store the multiplier in the appropriate field
|
||||
if (multiplierValue === 0){
|
||||
aggregator.immunityCount += 1;
|
||||
aggregator.immunities.push(storedMultiplier);
|
||||
} else if (multiplierValue === 0.5){
|
||||
aggregator.resistanceCount += 1;
|
||||
aggregator.resistances.push(storedMultiplier);
|
||||
} else if (multiplierValue === 2){
|
||||
aggregator.vulnerabilityCount += 1;
|
||||
aggregator.vulnerabilities.push(storedMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,21 @@ import getAggregatorResult from './getAggregatorResult.js';
|
||||
*/
|
||||
export default function computeImplicitVariable(node){
|
||||
const prop = {};
|
||||
|
||||
// Combine damage multipliers
|
||||
if (node.data.immunity){
|
||||
prop.immunity = node.data.immunity;
|
||||
prop.immunities = node.data.immunities;
|
||||
}
|
||||
if (node.data.resistance){
|
||||
prop.resistance = node.data.resistance;
|
||||
prop.resistances = node.data.resistances;
|
||||
}
|
||||
if (node.data.vulnerability){
|
||||
prop.vulnerability = node.data.vulnerability;
|
||||
prop.vulnerabilities = node.data.vulnerabilities;
|
||||
}
|
||||
|
||||
const result = getAggregatorResult(node);
|
||||
if (result !== undefined){
|
||||
prop.value = result;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import getAggregatorResult from './getAggregatorResult.js';
|
||||
|
||||
export default function computeVariableAsAttribute(computation, node, prop){
|
||||
let result = getAggregatorResult(node, prop) || 0;
|
||||
let result = getAggregatorResult(node) || 0;
|
||||
|
||||
prop.total = result;
|
||||
prop.value = prop.total - (prop.damage || 0);
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
|
||||
|
||||
export default function getAggregatorResult(node){
|
||||
// Work out the base value as the greater of the deining stat value or
|
||||
// the damage multiplier value
|
||||
// Work out the base value as the greater of the deining stat value
|
||||
// This baseValue comes from aggregating definitions
|
||||
let statBase = node.data.baseValue;
|
||||
|
||||
const damageMultiplyValue = node.data.damageMultiplyValue;
|
||||
if (statBase === undefined || damageMultiplyValue > statBase){
|
||||
statBase = damageMultiplyValue;
|
||||
}
|
||||
// get a reference to the aggregator
|
||||
const aggregator = node.data.effectAggregator;
|
||||
|
||||
|
||||
@@ -14,8 +14,9 @@ export default function computeCreature(creatureId){
|
||||
} catch (e){
|
||||
computation.errors.push({
|
||||
type: 'crash',
|
||||
details: e.reason,
|
||||
details: e.reason || e.message || e.toString(),
|
||||
});
|
||||
console.error(e);
|
||||
} finally {
|
||||
writeErrors(creatureId, computation.errors);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
|
||||
/*
|
||||
* DamageMultipliers are multipliers that affect how much damage is taken from
|
||||
@@ -20,6 +21,7 @@ let DamageMultiplierSchema = new SimpleSchema({
|
||||
'damageTypes.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
},
|
||||
// The value of the damage multiplier
|
||||
value: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
|
||||
const DamageSchema = createPropertySchema({
|
||||
// The roll that determines how much to damage the attribute
|
||||
@@ -24,6 +25,7 @@ const DamageSchema = createPropertySchema({
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
defaultValue: 'slashing',
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
:search-input.sync="searchInput"
|
||||
:disabled="isDisabled"
|
||||
:multiple="multiple"
|
||||
outlined
|
||||
@change="customChange"
|
||||
@focus="focused = true"
|
||||
@@ -24,12 +25,32 @@
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
multiple: Boolean,
|
||||
},
|
||||
data(){ return {
|
||||
searchInput: '',
|
||||
}},
|
||||
computed: {
|
||||
// This component gets a longer default debounce time because it's all
|
||||
// clicking no typing
|
||||
debounceTime() {
|
||||
if (Number.isFinite(this.debounce)){
|
||||
return this.debounce;
|
||||
} else if (Number.isFinite(this.context.debounceTime)){
|
||||
return this.context.debounceTime;
|
||||
} else {
|
||||
return 1000;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
customChange(val){
|
||||
this.change(val);
|
||||
if (this.multiple){
|
||||
this.input(val);
|
||||
} else {
|
||||
this.change(val);
|
||||
}
|
||||
this.searchInput = '';
|
||||
},
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ export default {
|
||||
data(){ return {
|
||||
error: false,
|
||||
ackErrors: null,
|
||||
rulesErrors: null,
|
||||
focused: false,
|
||||
loading: false,
|
||||
dirty: false,
|
||||
@@ -30,6 +31,7 @@ export default {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
rules: Array,
|
||||
},
|
||||
watch: {
|
||||
focused(newFocus){
|
||||
@@ -42,7 +44,11 @@ export default {
|
||||
// Start the loading bar on defocus if the input is dirty
|
||||
// It might be a lie, we aren't doing the work yet, but it feels laggy
|
||||
// to defocus an element and then it starts working after a delay
|
||||
if (!newFocus && this.dirty){
|
||||
if (
|
||||
!newFocus &&
|
||||
this.dirty &&
|
||||
!(this.rulesErrors && this.rulesErrors.length)
|
||||
){
|
||||
if (this.hasChangeListener) this.loading = true;
|
||||
}
|
||||
},
|
||||
@@ -54,7 +60,10 @@ export default {
|
||||
}
|
||||
},
|
||||
value(newValue){
|
||||
if (!this.focused){
|
||||
if (
|
||||
!this.focused &&
|
||||
!(this.rulesErrors && this.rulesErrors.length)
|
||||
){
|
||||
this.safeValue = newValue;
|
||||
}
|
||||
},
|
||||
@@ -69,6 +78,22 @@ export default {
|
||||
this.$emit('input', val);
|
||||
this.inputValue = val;
|
||||
this.dirty = true;
|
||||
|
||||
// Apply the rules if there are any
|
||||
this.rulesErrors = null;
|
||||
if (this.rules && this.rules.length){
|
||||
this.rules.forEach(rule => {
|
||||
const result = rule(val);
|
||||
if (typeof result === 'string'){
|
||||
if (!this.rulesErrors) this.rulesErrors = [];
|
||||
this.rulesErrors.push(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.rulesErrors){
|
||||
return;
|
||||
}
|
||||
|
||||
this.debouncedChange(val);
|
||||
},
|
||||
acknowledgeChange(error){
|
||||
@@ -106,6 +131,9 @@ export default {
|
||||
computed: {
|
||||
errors(){
|
||||
let errors = this.ackErrors ? [this.ackErrors] : [];
|
||||
if (Array.isArray(this.rulesErrors)){
|
||||
errors.push(...this.rulesErrors)
|
||||
}
|
||||
if (Array.isArray(this.errorMessages)){
|
||||
errors.push(...this.errorMessages);
|
||||
} else if (typeof this.errorMessages === 'string' && this.errorMessages){
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<damage-multiplier-card
|
||||
:multipliers="multipliers"
|
||||
@click-multiplier="clickProperty"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="appliedBuffs.length"
|
||||
class="buffs"
|
||||
@@ -199,10 +204,6 @@
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<div v-if="numKeys(creature.damageMultipliers)">
|
||||
<damage-multiplier-card :model="creature.damageMultipliers" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="savingThrows.length"
|
||||
class="saving-throws"
|
||||
@@ -367,7 +368,9 @@
|
||||
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
const getProperties = function(creature, filter){
|
||||
const getProperties = function(creature, filter, options = {
|
||||
sort: {order: 1}
|
||||
}){
|
||||
if (!creature) return;
|
||||
if (creature.settings.hideUnusedStats){
|
||||
filter.hide = {$ne: true};
|
||||
@@ -376,9 +379,8 @@
|
||||
filter.removed = {$ne: true};
|
||||
filter.inactive = {$ne: true};
|
||||
filter.overridden = {$ne: true};
|
||||
return CreatureProperties.find(filter, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
|
||||
return CreatureProperties.find(filter, options);
|
||||
};
|
||||
|
||||
const getAttributeOfType = function(creature, type){
|
||||
@@ -421,7 +423,7 @@
|
||||
}},
|
||||
meteor: {
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId);
|
||||
return Creatures.findOne(this.creatureId, {fields: {settings: 1}});
|
||||
},
|
||||
abilities(){
|
||||
return getAttributeOfType(this.creature, 'ability');
|
||||
@@ -484,6 +486,13 @@
|
||||
appliedBuffs(){
|
||||
return getProperties(this.creature, {type: 'buff'});
|
||||
},
|
||||
multipliers(){
|
||||
return getProperties(this.creature, {
|
||||
type: 'damageMultiplier'
|
||||
}, {
|
||||
sort: {value: 1, order: 1}
|
||||
});
|
||||
},
|
||||
attacks(){
|
||||
let props = getProperties(this.creature, {type: 'attack'})
|
||||
return props && props.map(attack => {
|
||||
@@ -511,10 +520,6 @@
|
||||
damageProperty.call({_id, operation: 'increment' ,value: -value});
|
||||
}
|
||||
},
|
||||
numKeys(obj){
|
||||
if (!obj) return 0;
|
||||
return Object.keys(obj).length;
|
||||
},
|
||||
softRemove(_id){
|
||||
softRemoveProperty.call({_id}, error => {
|
||||
if (error) console.error(error);
|
||||
|
||||
@@ -1,72 +1,96 @@
|
||||
<template lang="html">
|
||||
<v-card>
|
||||
<v-list
|
||||
three-line
|
||||
>
|
||||
<v-list-item v-if="weaknesses.length">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
Vulnerabilities
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ weaknesses }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="resistances.length">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
Resistances
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ resistances }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item v-if="immunities.length">
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
Immunities
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ immunities }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
<div>
|
||||
<v-card>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="multiplier in multipliers"
|
||||
:key="multiplier._id"
|
||||
:data-id="multiplier._id"
|
||||
@click="$emit('click-multiplier', {_id: multiplier._id})"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
{{ title(multiplier) }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="multiplier.name">
|
||||
{{ multiplier.name }}
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle class="d-flex flex-wrap align-center">
|
||||
<v-chip
|
||||
v-for="(damageType, index) in multiplier.damageTypes"
|
||||
:key="index"
|
||||
class="my-1 mr-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
outlined
|
||||
small
|
||||
label
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle
|
||||
v-if="multiplier.includeTags && multiplier.includeTags.length"
|
||||
class="d-flex flex-wrap align-center"
|
||||
>
|
||||
<div>
|
||||
For:
|
||||
</div>
|
||||
<v-chip
|
||||
v-for="(damageType, index) in multiplier.includeTags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
small
|
||||
outlined
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
<v-list-item-subtitle
|
||||
v-if="multiplier.excludeTags && multiplier.excludeTags.length"
|
||||
class="d-flex flex-wrap align-center"
|
||||
>
|
||||
<div>
|
||||
Except:
|
||||
</div>
|
||||
<v-chip
|
||||
v-for="(damageType, index) in multiplier.excludeTags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
small
|
||||
outlined
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
|
||||
export default {
|
||||
props: {
|
||||
model:{
|
||||
type: Object,
|
||||
multipliers:{
|
||||
type: Array,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
weaknesses(){
|
||||
return getKeysOfValue(this.model, 2).join(', ');
|
||||
},
|
||||
resistances(){
|
||||
return getKeysOfValue(this.model, 0.5).join(', ');
|
||||
},
|
||||
immunities(){
|
||||
return getKeysOfValue(this.model, 0).join(', ');
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function getKeysOfValue(object, value){
|
||||
let keys = [];
|
||||
for (let key in object){
|
||||
if (object[key] === value){
|
||||
keys.push(key);
|
||||
methods: {
|
||||
title(prop){
|
||||
switch (prop.value){
|
||||
case 0: return 'Immunity';
|
||||
case 0.5: return 'Resistance';
|
||||
case 2: return 'Vulnerability';
|
||||
}
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
label="Damage Type"
|
||||
style="flex-basis: 200px;"
|
||||
hint="Use the Healing type to restore hit points"
|
||||
:rules="damageTypeRules"
|
||||
:items="DAMAGE_TYPES"
|
||||
:value="model.damageType"
|
||||
:error-messages="errors.damageType"
|
||||
@@ -46,7 +47,8 @@
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
hint="Used to let slots find this property in a library, should otherwise be left blank"
|
||||
hint=""
|
||||
:items="['magical', 'silvered']"
|
||||
:value="model.tags"
|
||||
:error-messages="errors.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
@@ -57,6 +59,7 @@
|
||||
<script lang="js">
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyFormMixin],
|
||||
@@ -68,6 +71,14 @@ export default {
|
||||
},
|
||||
data(){return{
|
||||
DAMAGE_TYPES,
|
||||
damageTypeRules: [
|
||||
value => {
|
||||
if (!value) return 'Damage type is required';
|
||||
if (!VARIABLE_NAME_REGEX.test(value)){
|
||||
return `${value} is not a valid damage name`
|
||||
}
|
||||
}
|
||||
],
|
||||
}},
|
||||
computed: {
|
||||
targetOptions(){
|
||||
|
||||
@@ -1,93 +1,102 @@
|
||||
<template lang="html">
|
||||
<div class="attribute-form">
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<div class="layout wrap">
|
||||
<smart-select
|
||||
label="Damage Type"
|
||||
style="flex-basis: 300px;"
|
||||
multiple
|
||||
:items="damageTypes"
|
||||
:value="model.damageTypes"
|
||||
:error-messages="errors.damageTypes"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('damageTypes', ...arguments)"
|
||||
/>
|
||||
<smart-select
|
||||
label="Value"
|
||||
style="flex-basis: 300px;"
|
||||
:items="values"
|
||||
:value="model.value"
|
||||
:error-messages="errors.value"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('value', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
hint="Used to let slots find this property in a library, should otherwise be left blank"
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<smart-select
|
||||
label="Value"
|
||||
style="flex-basis: 300px;"
|
||||
:items="values"
|
||||
:value="model.value"
|
||||
:error-messages="errors.value"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('value', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12">
|
||||
<smart-combobox
|
||||
label="Damage Types"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:rules="damageTypeRules"
|
||||
:items="DAMAGE_TYPES"
|
||||
:value="model.damageTypes"
|
||||
:error-messages="errors.damageTypes"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@update:error="error"
|
||||
@change="change('damageTypes', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<form-sections>
|
||||
<form-section
|
||||
name="Advanced"
|
||||
>
|
||||
<smart-combobox
|
||||
label="Damage tags required"
|
||||
hint="This damage multiplier will only apply to damage that has all of these tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:items="['magical', 'silvered']"
|
||||
:value="model.includeTags"
|
||||
@change="change('includeTags', ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Damage tags excluded"
|
||||
hint="Damage that includes any of these tags will bypass this damage multiplier"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
:items="['magical', 'silvered']"
|
||||
:value="model.excludeTags"
|
||||
@change="change('excludeTags', ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
hint=""
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import FormSection, { FormSections } from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
|
||||
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSections,
|
||||
FormSection,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
data(){return {
|
||||
damageTypes: [
|
||||
{
|
||||
value: 'bludgeoning',
|
||||
text: 'Bludgeoning',
|
||||
}, {
|
||||
value: 'piercing',
|
||||
text: 'Piercing',
|
||||
}, {
|
||||
value: 'slashing',
|
||||
text: 'Slashing',
|
||||
}, {
|
||||
value: 'acid',
|
||||
text: 'Acid',
|
||||
}, {
|
||||
value: 'cold',
|
||||
text: 'Cold',
|
||||
}, {
|
||||
value: 'fire',
|
||||
text: 'Fire',
|
||||
}, {
|
||||
value: 'force',
|
||||
text: 'Force',
|
||||
}, {
|
||||
value: 'lightning',
|
||||
text: 'Lightning',
|
||||
}, {
|
||||
value: 'necrotic',
|
||||
text: 'Necrotic',
|
||||
}, {
|
||||
value: 'poison',
|
||||
text: 'Poison',
|
||||
}, {
|
||||
value: 'psychic',
|
||||
text: 'Psychic',
|
||||
}, {
|
||||
value: 'radiant',
|
||||
text: 'Radiant',
|
||||
}, {
|
||||
value: 'thunder',
|
||||
text: 'Thunder',
|
||||
},
|
||||
],
|
||||
DAMAGE_TYPES,
|
||||
values: [
|
||||
{
|
||||
value: 0,
|
||||
@@ -100,7 +109,23 @@
|
||||
text: 'Vulnerability',
|
||||
},
|
||||
],
|
||||
damageTypeRules: [
|
||||
value => {
|
||||
if (value && value.length){
|
||||
for(let i = 0; i < value.length; i++){
|
||||
if (!VARIABLE_NAME_REGEX.test(value[i])){
|
||||
return `${value[i]} is not a valid damage name`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
};},
|
||||
methods: {
|
||||
error(e){
|
||||
console.log({e})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,13 +2,57 @@
|
||||
<div>
|
||||
<v-row dense>
|
||||
<property-field
|
||||
name="Operation"
|
||||
name="Value"
|
||||
:value="operation"
|
||||
/>
|
||||
<property-field
|
||||
name="Damage types"
|
||||
:value="model.damageTypes.join(', ')"
|
||||
/>
|
||||
>
|
||||
<v-chip
|
||||
v-for="(damageType, index) in model.damageTypes"
|
||||
:key="index"
|
||||
class="my-1 mr-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
outlined
|
||||
small
|
||||
label
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</property-field>
|
||||
<property-field
|
||||
v-if="model.includeTags && model.includeTags.length"
|
||||
name="Damage tags required"
|
||||
>
|
||||
<v-chip
|
||||
v-for="(damageType, index) in model.includeTags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
small
|
||||
outlined
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</property-field>
|
||||
<property-field
|
||||
v-if="model.excludeTags && model.excludeTags.length"
|
||||
name="Damage tags excluded"
|
||||
>
|
||||
<v-chip
|
||||
v-for="(damageType, index) in model.excludeTags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
style="cursor: pointer"
|
||||
:input-value="true"
|
||||
small
|
||||
outlined
|
||||
>
|
||||
{{ damageType }}
|
||||
</v-chip>
|
||||
</property-field>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user