Added point buy to computation engine
This commit is contained in:
@@ -14,6 +14,7 @@ const linkDependenciesByType = {
|
|||||||
effect: linkEffects,
|
effect: linkEffects,
|
||||||
proficiency: linkProficiencies,
|
proficiency: linkProficiencies,
|
||||||
roll: linkRoll,
|
roll: linkRoll,
|
||||||
|
pointBuy: linkPointBuy,
|
||||||
propertySlot: linkSlot,
|
propertySlot: linkSlot,
|
||||||
skill: linkSkill,
|
skill: linkSkill,
|
||||||
spell: linkAction,
|
spell: linkAction,
|
||||||
@@ -242,6 +243,23 @@ function linkDamageMultiplier(dependencyGraph, prop) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkPointBuy(dependencyGraph, prop){
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'min' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'max' });
|
||||||
|
dependOnCalc({ dependencyGraph, prop, key: 'cost' });
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
row.type = 'pointBuyRow';
|
||||||
|
row.tableName = prop.name;
|
||||||
|
row.tableId = prop._id;
|
||||||
|
dependencyGraph.addNode(row._id, row);
|
||||||
|
linkVariableName(dependencyGraph, row);
|
||||||
|
dependOnCalc({ dependencyGraph, row, key: 'min' });
|
||||||
|
dependOnCalc({ dependencyGraph, row, key: 'max' });
|
||||||
|
dependOnCalc({ dependencyGraph, row, key: 'cost' });
|
||||||
|
});
|
||||||
|
if (prop.inactive) return;
|
||||||
|
}
|
||||||
|
|
||||||
function linkProficiencies(dependencyGraph, prop){
|
function linkProficiencies(dependencyGraph, prop){
|
||||||
// The stats depend on the proficiency
|
// The stats depend on the proficiency
|
||||||
if (prop.inactive) return;
|
if (prop.inactive) return;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import _variable from './computeByType/computeVariable.js';
|
|||||||
import action from './computeByType/computeAction.js';
|
import action from './computeByType/computeAction.js';
|
||||||
import attribute from './computeByType/computeAttribute.js';
|
import attribute from './computeByType/computeAttribute.js';
|
||||||
import skill from './computeByType/computeSkill.js';
|
import skill from './computeByType/computeSkill.js';
|
||||||
|
import pointBuy from './computeByType/computePointBuy.js';
|
||||||
import propertySlot from './computeByType/computeSlot.js';
|
import propertySlot from './computeByType/computeSlot.js';
|
||||||
import container from './computeByType/computeContainer.js';
|
import container from './computeByType/computeContainer.js';
|
||||||
import _calculation from './computeByType/computeCalculation.js';
|
import _calculation from './computeByType/computeCalculation.js';
|
||||||
@@ -13,6 +14,7 @@ export default Object.freeze({
|
|||||||
attribute,
|
attribute,
|
||||||
container,
|
container,
|
||||||
skill,
|
skill,
|
||||||
|
pointBuy,
|
||||||
propertySlot,
|
propertySlot,
|
||||||
spell: action,
|
spell: action,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { has } from 'lodash';
|
||||||
|
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||||
|
|
||||||
|
export default function computePointBuy(computation, node) {
|
||||||
|
const prop = node.data;
|
||||||
|
const tableMin = prop.min?.value || null;
|
||||||
|
const tableMax = prop.max?.value || null;
|
||||||
|
prop.spent = 0;
|
||||||
|
prop.values?.forEach(row => {
|
||||||
|
// Clean up added properties
|
||||||
|
delete row.tableId;
|
||||||
|
delete row.tableName;
|
||||||
|
delete row.type;
|
||||||
|
|
||||||
|
row.spent = 0;
|
||||||
|
if (row.value === undefined) return;
|
||||||
|
const min = has(row, 'min.value') ? row.min.value : tableMin;
|
||||||
|
const max = has(row, 'max.value') ? row.max.value : tableMax;
|
||||||
|
const costFunction = EJSON.clone(row.cost || prop.cost);
|
||||||
|
if (costFunction) costFunction.parseLevel = 'reduce';
|
||||||
|
|
||||||
|
// Check min and max
|
||||||
|
if (min !== null && row.value < min) {
|
||||||
|
row.errors = row.errors || [];
|
||||||
|
row.errors.push('Value smaller than min value');
|
||||||
|
}
|
||||||
|
if (max !== null && row.value > max) {
|
||||||
|
row.errors = row.errors || [];
|
||||||
|
row.errors.push('Value larger than max value');
|
||||||
|
}
|
||||||
|
// Evaluate the cost function
|
||||||
|
if (!costFunction) return;
|
||||||
|
evaluateCalculation(costFunction, { ...computation.scope, value: row.value });
|
||||||
|
// Write calculation errors
|
||||||
|
costFunction.errors?.forEach(error => {
|
||||||
|
if (error?.message) {
|
||||||
|
row.errors = row.errors || [];
|
||||||
|
row.errors.push(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (Number.isFinite(costFunction.value)) {
|
||||||
|
row.spent = costFunction.value;
|
||||||
|
prop.spent += costFunction.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (prop.spent > prop.total?.value) {
|
||||||
|
prop.errors = prop.errors || [];
|
||||||
|
prop.errors.push('Spent more than total points available')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
// get current defining prop
|
// get current defining prop
|
||||||
const definingProp = node.data.definingProp;
|
const definingProp = node.data.definingProp;
|
||||||
// Find the last defining prop
|
// Find the last defining prop
|
||||||
if (!definingProp || prop.order > definingProp.order){
|
if (
|
||||||
|
!definingProp ||
|
||||||
|
prop.type !== 'pointBuyRow' && (
|
||||||
|
definingProp.type === 'pointBuyRow' ||
|
||||||
|
prop.order > definingProp.order
|
||||||
|
)
|
||||||
|
) {
|
||||||
// override the current defining prop
|
// override the current defining prop
|
||||||
overrideProp(definingProp, node);
|
overrideProp(definingProp, node);
|
||||||
// set this prop as the new defining prop
|
// set this prop as the new defining prop
|
||||||
@@ -18,9 +24,32 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Aggregate the base value due to the defining properties
|
// Aggregate the base value due to the defining properties
|
||||||
const propBaseValue = prop.baseValue?.value;
|
let propBaseValue = prop.baseValue?.value;
|
||||||
|
// Point buy rows use prop.value instead of prop.baseValue
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
propBaseValue = prop.value;
|
||||||
|
}
|
||||||
|
|
||||||
if (propBaseValue === undefined) return;
|
if (propBaseValue === undefined) return;
|
||||||
|
// Store a summary of the definition as a base value effect
|
||||||
|
node.data.effects = node.data.effects || [];
|
||||||
|
if (prop.type === 'pointBuyRow') {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop.tableId,
|
||||||
|
name: prop.tableName,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: 'pointBuy',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
node.data.effects.push({
|
||||||
|
_id: prop._id,
|
||||||
|
name: prop.name,
|
||||||
|
operation: 'base',
|
||||||
|
amount: { value: propBaseValue },
|
||||||
|
type: prop.type,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
if (node.data.baseValue === undefined || propBaseValue > node.data.baseValue){
|
||||||
node.data.baseValue = propBaseValue;
|
node.data.baseValue = propBaseValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export default function aggregateEffect({node, linkedNode, link}){
|
|||||||
name: linkedNode.data.name,
|
name: linkedNode.data.name,
|
||||||
operation: linkedNode.data.operation,
|
operation: linkedNode.data.operation,
|
||||||
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
||||||
|
type: linkedNode.data.type,
|
||||||
// ancestors: linkedNode.data.ancestors,
|
// ancestors: linkedNode.data.ancestors,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
|||||||
effects: {
|
effects: {
|
||||||
type: Array,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
'effects.$': {
|
'effects.$': {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -13,13 +13,6 @@ let PointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
max: STORAGE_LIMITS.name,
|
max: STORAGE_LIMITS.name,
|
||||||
},
|
},
|
||||||
variableName: {
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
regEx: VARIABLE_NAME_REGEX,
|
|
||||||
min: 2,
|
|
||||||
max: STORAGE_LIMITS.variableName,
|
|
||||||
},
|
|
||||||
ignored: {
|
ignored: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
optional: true,
|
optional: true,
|
||||||
@@ -82,6 +75,7 @@ let PointBuySchema = createPropertySchema({
|
|||||||
cost: {
|
cost: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -97,6 +91,7 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
cost: {
|
cost: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
},
|
},
|
||||||
'values': {
|
'values': {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -117,6 +112,20 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
'values.$.cost': {
|
'values.$.cost': {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
optional: true,
|
optional: true,
|
||||||
|
parseLevel: 'compile',
|
||||||
|
},
|
||||||
|
'values.$.spent': {
|
||||||
|
type: Number,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors': {
|
||||||
|
type: Array,
|
||||||
|
optional: true,
|
||||||
|
removeBeforeCompute: true,
|
||||||
|
},
|
||||||
|
'values.$.errors.$': {
|
||||||
|
type: String,
|
||||||
},
|
},
|
||||||
total: {
|
total: {
|
||||||
type: 'computedOnlyField',
|
type: 'computedOnlyField',
|
||||||
@@ -127,11 +136,14 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
|||||||
optional: true,
|
optional: true,
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
error: {
|
errors: {
|
||||||
type: String,
|
type: Array,
|
||||||
optional: true,
|
optional: true,
|
||||||
removeBeforeCompute: true,
|
removeBeforeCompute: true,
|
||||||
},
|
},
|
||||||
|
'errors.$': {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const ComputedPointBuySchema = new SimpleSchema()
|
const ComputedPointBuySchema = new SimpleSchema()
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
Next
|
Next
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
:disabled="biographyAlert"
|
:disabled="!!biographyAlert"
|
||||||
:text="step < 2"
|
:text="step < 2"
|
||||||
:color="step < 2? '' : 'accent'"
|
:color="step < 2? '' : 'accent'"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
|
|||||||
@@ -27,9 +27,9 @@
|
|||||||
<div class="text-body-1 mb-1">
|
<div class="text-body-1 mb-1">
|
||||||
{{ displayedText }}
|
{{ displayedText }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hideBreadcrumbs && model.ancestors">
|
<div v-if="!hideBreadcrumbs && ancestors">
|
||||||
<breadcrumbs
|
<breadcrumbs
|
||||||
:model="model"
|
:model="{...model, ancestors}"
|
||||||
class="text-caption"
|
class="text-caption"
|
||||||
no-links
|
no-links
|
||||||
no-icons
|
no-icons
|
||||||
@@ -41,94 +41,101 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
|
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
|
||||||
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
|
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
|
||||||
import { isFinite } from 'lodash';
|
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||||
|
import { isFinite } from 'lodash';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
hideBreadcrumbs: Boolean,
|
||||||
|
model: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
props: {
|
},
|
||||||
hideBreadcrumbs: Boolean,
|
computed: {
|
||||||
model: {
|
hasClickListener(){
|
||||||
type: Object,
|
return this.$listeners && this.$listeners.click
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
displayedText(){
|
||||||
hasClickListener(){
|
if (this.model.operation === 'conditional'){
|
||||||
return this.$listeners && this.$listeners.click
|
return this.model.text || this.model.name || this.operation
|
||||||
},
|
} else {
|
||||||
displayedText(){
|
return this.model.name || this.operation
|
||||||
if (this.model.operation === 'conditional'){
|
|
||||||
return this.model.text || this.model.name || this.operation
|
|
||||||
} else {
|
|
||||||
return this.model.name || this.operation
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolvedValue(){
|
|
||||||
let amount = this.model.amount;
|
|
||||||
if (!amount) return;
|
|
||||||
return amount.value !== undefined ? amount.value : amount.calculation;
|
|
||||||
},
|
|
||||||
effectIcon(){
|
|
||||||
let value = this.resolvedValue;
|
|
||||||
return getEffectIcon(this.model.operation, value);
|
|
||||||
},
|
|
||||||
operation(){
|
|
||||||
switch(this.model.operation) {
|
|
||||||
case 'base': return 'Base value';
|
|
||||||
case 'add': return 'Add';
|
|
||||||
case 'mul': return 'Multiply';
|
|
||||||
case 'min': return 'Minimum';
|
|
||||||
case 'max': return 'Maximum';
|
|
||||||
case 'advantage': return 'Advantage';
|
|
||||||
case 'disadvantage': return 'Disadvantage';
|
|
||||||
case 'passiveAdd': return 'Passive bonus';
|
|
||||||
case 'fail': return 'Always fail';
|
|
||||||
case 'conditional': return 'Conditional benefit' ;
|
|
||||||
default: return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showValue(){
|
|
||||||
switch(this.model.operation) {
|
|
||||||
case 'base': return true;
|
|
||||||
case 'add': return true;
|
|
||||||
case 'mul': return true;
|
|
||||||
case 'min': return true;
|
|
||||||
case 'max': return true;
|
|
||||||
case 'advantage': return false;
|
|
||||||
case 'disadvantage': return false;
|
|
||||||
case 'passiveAdd': return true;
|
|
||||||
case 'fail': return false;
|
|
||||||
case 'conditional': return false;
|
|
||||||
default: return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
displayedValue(){
|
|
||||||
let value = this.resolvedValue;
|
|
||||||
switch(this.model.operation) {
|
|
||||||
case 'base': return value;
|
|
||||||
case 'add': return isFinite(value) ? Math.abs(value) : value;
|
|
||||||
case 'mul': return value;
|
|
||||||
case 'min': return value;
|
|
||||||
case 'max': return value;
|
|
||||||
case 'advantage': return;
|
|
||||||
case 'disadvantage': return;
|
|
||||||
case 'passiveAdd': return isFinite(value) ? Math.abs(value) : value;
|
|
||||||
case 'fail': return;
|
|
||||||
case 'conditional': return undefined;
|
|
||||||
default: return undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
resolvedValue() {
|
||||||
click(e){
|
let amount = this.model.amount;
|
||||||
this.$emit('click', e);
|
if (!amount) return;
|
||||||
},
|
return amount.value !== undefined ? amount.value : amount.calculation;
|
||||||
},
|
},
|
||||||
};
|
effectIcon(){
|
||||||
|
let value = this.resolvedValue;
|
||||||
|
return getEffectIcon(this.model.operation, value);
|
||||||
|
},
|
||||||
|
operation(){
|
||||||
|
switch(this.model.operation) {
|
||||||
|
case 'base': return 'Base value';
|
||||||
|
case 'add': return 'Add';
|
||||||
|
case 'mul': return 'Multiply';
|
||||||
|
case 'min': return 'Minimum';
|
||||||
|
case 'max': return 'Maximum';
|
||||||
|
case 'advantage': return 'Advantage';
|
||||||
|
case 'disadvantage': return 'Disadvantage';
|
||||||
|
case 'passiveAdd': return 'Passive bonus';
|
||||||
|
case 'fail': return 'Always fail';
|
||||||
|
case 'conditional': return 'Conditional benefit' ;
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showValue(){
|
||||||
|
switch(this.model.operation) {
|
||||||
|
case 'base': return true;
|
||||||
|
case 'add': return true;
|
||||||
|
case 'mul': return true;
|
||||||
|
case 'min': return true;
|
||||||
|
case 'max': return true;
|
||||||
|
case 'advantage': return false;
|
||||||
|
case 'disadvantage': return false;
|
||||||
|
case 'passiveAdd': return true;
|
||||||
|
case 'fail': return false;
|
||||||
|
case 'conditional': return false;
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
displayedValue(){
|
||||||
|
let value = this.resolvedValue;
|
||||||
|
switch(this.model.operation) {
|
||||||
|
case 'base': return value;
|
||||||
|
case 'add': return isFinite(value) ? Math.abs(value) : value;
|
||||||
|
case 'mul': return value;
|
||||||
|
case 'min': return value;
|
||||||
|
case 'max': return value;
|
||||||
|
case 'advantage': return;
|
||||||
|
case 'disadvantage': return;
|
||||||
|
case 'passiveAdd': return isFinite(value) ? Math.abs(value) : value;
|
||||||
|
case 'fail': return;
|
||||||
|
case 'conditional': return undefined;
|
||||||
|
default: return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
meteor: {
|
||||||
|
ancestors() {
|
||||||
|
const prop = CreatureProperties.findOne(this.model._id);
|
||||||
|
return prop && prop.ancestors || [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click(e){
|
||||||
|
this.$emit('click', e);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="css" scoped>
|
<style lang="css" scoped>
|
||||||
|
|||||||
@@ -1,86 +1,172 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<div class="point-buy-form">
|
<div class="point-buy-form">
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
<text-field
|
<v-col
|
||||||
ref="focusFirst"
|
cols="12"
|
||||||
label="Name"
|
md="6"
|
||||||
:value="model.name"
|
|
||||||
:error-messages="errors.name"
|
|
||||||
@change="change('name', ...arguments)"
|
|
||||||
/>
|
|
||||||
<text-field
|
|
||||||
label="Variable name"
|
|
||||||
:value="model.variableName"
|
|
||||||
hint="Use this name in calculations to reference this point buy table"
|
|
||||||
:error-messages="errors.variableName"
|
|
||||||
@change="change('variableName', ...arguments)"
|
|
||||||
/>
|
|
||||||
<computed-field
|
|
||||||
label="Min"
|
|
||||||
hint="The minimum value for each row"
|
|
||||||
:model="model.min"
|
|
||||||
:error-messages="errors.min"
|
|
||||||
@change="change('min', ...arguments)"
|
|
||||||
/>
|
|
||||||
<computed-field
|
|
||||||
label="Max"
|
|
||||||
hint="The maximum value for each row"
|
|
||||||
:model="model.max"
|
|
||||||
:error-messages="errors.max"
|
|
||||||
@change="change('max', ...arguments)"
|
|
||||||
/>
|
|
||||||
<computed-field
|
|
||||||
label="Cost"
|
|
||||||
hint="A function of `value` that determines the cost of each row"
|
|
||||||
:model="model.cost"
|
|
||||||
:error-messages="errors.cost"
|
|
||||||
@change="change('cost', ...arguments)"
|
|
||||||
/>
|
|
||||||
<computed-field
|
|
||||||
label="Total"
|
|
||||||
hint="The total allowed cost of all rows"
|
|
||||||
:model="model.total"
|
|
||||||
:error-messages="errors.total"
|
|
||||||
@change="change('total', ...arguments)"
|
|
||||||
/>
|
|
||||||
</v-row>
|
|
||||||
<v-row
|
|
||||||
v-for="(row, i) in model.values"
|
|
||||||
:key="row._id"
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
<text-field
|
|
||||||
ref="focusFirst"
|
|
||||||
label="Name"
|
|
||||||
:value="model.name"
|
|
||||||
:error-messages="errors.name"
|
|
||||||
@change="change(['values', i, 'name'], ...arguments)"
|
|
||||||
/>
|
|
||||||
<text-field
|
|
||||||
label="Variable name"
|
|
||||||
:value="model.variableName"
|
|
||||||
hint="Use this name to reference this row of the table: tableVariableName.thisVariableName"
|
|
||||||
:error-messages="errors.variableName"
|
|
||||||
@change="change(['values', i, 'variableName'], ...arguments)"
|
|
||||||
/>
|
|
||||||
<v-btn
|
|
||||||
icon
|
|
||||||
@click="$emit('pull', {path: ['values', i]})"
|
|
||||||
>
|
>
|
||||||
<v-icon>mdi-delete</v-icon>
|
<text-field
|
||||||
</v-btn>
|
ref="focusFirst"
|
||||||
|
label="Table name"
|
||||||
|
:value="model.name"
|
||||||
|
:error-messages="errors.name"
|
||||||
|
@change="change('name', ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<computed-field
|
||||||
|
label="Min"
|
||||||
|
hint="The minimum value for each row"
|
||||||
|
:model="model.min"
|
||||||
|
:error-messages="errors.min"
|
||||||
|
@change="({path, value, ack}) =>
|
||||||
|
$emit('change', {path: ['min', ...path], value, ack})"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<computed-field
|
||||||
|
label="Max"
|
||||||
|
hint="The maximum value for each row"
|
||||||
|
:model="model.max"
|
||||||
|
:error-messages="errors.max"
|
||||||
|
@change="({path, value, ack}) =>
|
||||||
|
$emit('change', {path: ['max', ...path], value, ack})"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<computed-field
|
||||||
|
label="Cost"
|
||||||
|
hint="A function of `value` that determines the cost of each row"
|
||||||
|
hide-value
|
||||||
|
:model="model.cost"
|
||||||
|
:error-messages="errors.cost"
|
||||||
|
@change="({path, value, ack}) =>
|
||||||
|
$emit('change', {path: ['cost', ...path], value, ack})"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<computed-field
|
||||||
|
label="Total available points"
|
||||||
|
hint="The total allowed cost of all rows"
|
||||||
|
:model="model.total"
|
||||||
|
:error-messages="errors.total"
|
||||||
|
@change="({path, value, ack}) =>
|
||||||
|
$emit('change', {path: ['total', ...path], value, ack})"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-btn
|
<v-subheader>
|
||||||
icon
|
Rows
|
||||||
outlined
|
</v-subheader>
|
||||||
:loading="addRowLoading"
|
<v-slide-x-transition
|
||||||
:disabled="rowsFull"
|
group
|
||||||
@click="addRow"
|
leave-absolute
|
||||||
>
|
>
|
||||||
<v-icon>
|
<v-row
|
||||||
mdi-plus
|
v-for="(row, i) in model.values"
|
||||||
</v-icon>
|
:key="row._id"
|
||||||
</v-btn>
|
dense
|
||||||
|
>
|
||||||
|
<v-divider
|
||||||
|
v-if="i"
|
||||||
|
style="flex-basis: 100%;"
|
||||||
|
class="mb-6"
|
||||||
|
/>
|
||||||
|
<v-col cols="11">
|
||||||
|
<v-row dense>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<text-field
|
||||||
|
ref="focusFirst"
|
||||||
|
label="Row Name"
|
||||||
|
:value="row.name"
|
||||||
|
:error-messages="errors.values && errors.values[i] && errors.values[i].name"
|
||||||
|
@change="change(['values', i, 'name'], ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<text-field
|
||||||
|
label="Variable name"
|
||||||
|
:value="row.variableName"
|
||||||
|
hint="Use this name in calculations to reference this row of the table"
|
||||||
|
:error-messages="errors.values && errors.values[i] && errors.values[i].variableName"
|
||||||
|
@change="change(['values', i, 'variableName'], ...arguments)"
|
||||||
|
/>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="12"
|
||||||
|
md="6"
|
||||||
|
>
|
||||||
|
<text-field
|
||||||
|
label="Value"
|
||||||
|
type="number"
|
||||||
|
:min="row.hasOwnProperty('min') ? row.min && row.min.value : model.min && model.min.value"
|
||||||
|
:max="row.hasOwnProperty('max') ? row.max && row.max.value : model.max && model.max.value"
|
||||||
|
:value="row.value"
|
||||||
|
:error-messages="errors.values && errors.values[i] && errors.values[i].value"
|
||||||
|
@change="(value, ack) => $emit('change', {path: ['values', i, 'value'], value, ack})"
|
||||||
|
>
|
||||||
|
<template v-if="row.spent">
|
||||||
|
Cost: {{ row.spent }}
|
||||||
|
</template>
|
||||||
|
</text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-col>
|
||||||
|
<v-col
|
||||||
|
cols="1"
|
||||||
|
class="d-flex align-center justify-center"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
large
|
||||||
|
@click="$emit('pull', {path: ['values', i]})"
|
||||||
|
>
|
||||||
|
<v-icon>mdi-delete</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row
|
||||||
|
key="addButton"
|
||||||
|
dense
|
||||||
|
justify="end"
|
||||||
|
class="mb-4"
|
||||||
|
>
|
||||||
|
<v-col
|
||||||
|
cols="1"
|
||||||
|
class="d-flex justify-center"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
icon
|
||||||
|
outlined
|
||||||
|
:loading="addRowLoading"
|
||||||
|
:disabled="rowsFull"
|
||||||
|
@click="addRow"
|
||||||
|
>
|
||||||
|
<v-icon>
|
||||||
|
mdi-plus
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-slide-x-transition>
|
||||||
<form-section
|
<form-section
|
||||||
v-if="$slots.children"
|
v-if="$slots.children"
|
||||||
name="Children"
|
name="Children"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
@change="(value, ack) => $emit('change', {path: ['calculation'], value, ack})"
|
@change="(value, ack) => $emit('change', {path: ['calculation'], value, ack})"
|
||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="model.value !== undefined || model.value !== null"
|
v-if="showValue"
|
||||||
#value
|
#value
|
||||||
>
|
>
|
||||||
{{ model.value }}
|
{{ model.value }}
|
||||||
@@ -28,8 +28,20 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
},
|
},
|
||||||
|
hideValue: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
showValue() {
|
||||||
|
const value = this.model.value;
|
||||||
|
if (
|
||||||
|
this.hideValue ||
|
||||||
|
(value === undefined || value === null) ||
|
||||||
|
value == this.model.calculation
|
||||||
|
) return false;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
errorList(){
|
errorList(){
|
||||||
if (this.model.parseError){
|
if (this.model.parseError){
|
||||||
return [this.model.parseError, ...this.model.errors];
|
return [this.model.parseError, ...this.model.errors];
|
||||||
|
|||||||
@@ -107,25 +107,18 @@
|
|||||||
</v-row>
|
</v-row>
|
||||||
<v-row dense>
|
<v-row dense>
|
||||||
<property-field
|
<property-field
|
||||||
v-if="baseEffects.length || effects.length"
|
v-if="effects && effects.length"
|
||||||
:cols="{col: 12}"
|
:cols="{col: 12}"
|
||||||
name="Effects"
|
name="Effects"
|
||||||
>
|
>
|
||||||
<v-list style="width: 100%;">
|
<v-list style="width: 100%;">
|
||||||
<attribute-effect
|
|
||||||
v-for="effect in baseEffects"
|
|
||||||
:key="effect._id"
|
|
||||||
:model="effect"
|
|
||||||
:hide-breadcrumbs="effect._id === model._id"
|
|
||||||
:data-id="effect._id"
|
|
||||||
@click="effect._id !== model._id && clickEffect(effect._id)"
|
|
||||||
/>
|
|
||||||
<attribute-effect
|
<attribute-effect
|
||||||
v-for="effect in effects"
|
v-for="effect in effects"
|
||||||
:key="effect._id"
|
:key="effect._id"
|
||||||
:model="effect"
|
:model="effect"
|
||||||
:data-id="effect._id"
|
:data-id="effect._id"
|
||||||
@click="clickEffect(effect._id)"
|
:hide-breadcrumbs="effect._id === model._id"
|
||||||
|
@click="effect._id !== model._id && clickEffect(effect._id)"
|
||||||
/>
|
/>
|
||||||
</v-list>
|
</v-list>
|
||||||
</property-field>
|
</property-field>
|
||||||
@@ -137,7 +130,6 @@
|
|||||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||||
import AttributeEffect from '/imports/ui/properties/components/attributes/AttributeEffect.vue';
|
import AttributeEffect from '/imports/ui/properties/components/attributes/AttributeEffect.vue';
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
import damageProperty from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||||
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
import IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||||
import getProficiencyIcon from '/imports/ui/utility/getProficiencyIcon.js';
|
import getProficiencyIcon from '/imports/ui/utility/getProficiencyIcon.js';
|
||||||
@@ -211,31 +203,8 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
meteor: {
|
meteor: {
|
||||||
baseEffects(){
|
|
||||||
if (this.context.creatureId && this.model.variableName){
|
|
||||||
let creatureId = this.context.creatureId;
|
|
||||||
return CreatureProperties.find({
|
|
||||||
'ancestors.id': creatureId,
|
|
||||||
type: 'attribute',
|
|
||||||
variableName: this.model.variableName,
|
|
||||||
removed: {$ne: true},
|
|
||||||
inactive: {$ne: true},
|
|
||||||
}).map( prop => ({
|
|
||||||
_id: prop._id,
|
|
||||||
name: 'Attribute base value',
|
|
||||||
operation: 'base',
|
|
||||||
amount: prop.baseValue,
|
|
||||||
stats: [prop.variableName],
|
|
||||||
ancestors: prop.ancestors,
|
|
||||||
}) ).filter(effect => effect.amount);
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
effects() {
|
effects() {
|
||||||
return CreatureProperties.find({
|
return this.model.effects;
|
||||||
_id: { $in: this.model.effects?.map(e => e._id) || [] }
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
45
app/imports/ui/properties/viewers/PointBuyViewer.vue
Normal file
45
app/imports/ui/properties/viewers/PointBuyViewer.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<div class="point-buy-viewer">
|
||||||
|
<v-row dense>
|
||||||
|
<property-field
|
||||||
|
v-for="(row, i) in model.values"
|
||||||
|
:key="row._id"
|
||||||
|
:name="row.name"
|
||||||
|
:value="row.value"
|
||||||
|
/>
|
||||||
|
</v-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="js">
|
||||||
|
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||||
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
|
import { timingOptions, eventOptions, actionPropertyTypeOptions } from '/imports/api/properties/Triggers.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [propertyViewerMixin],
|
||||||
|
inject: {
|
||||||
|
context: {
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
slotTypeName(){
|
||||||
|
if (!this.model.slotType) return;
|
||||||
|
return getPropertyName(this.model.slotType);
|
||||||
|
},
|
||||||
|
timingText(){
|
||||||
|
if (!this.model.timing) return;
|
||||||
|
return timingOptions[this.model.timing];
|
||||||
|
},
|
||||||
|
actionPropertyText(){
|
||||||
|
if (!this.model.actionPropertyType) return;
|
||||||
|
return actionPropertyTypeOptions[this.model.actionPropertyType];
|
||||||
|
},
|
||||||
|
eventText(){
|
||||||
|
if (!this.model.event) return;
|
||||||
|
return eventOptions[this.model.event];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -15,6 +15,7 @@ const FeatureViewer = () => import ('/imports/ui/properties/viewers/FeatureViewe
|
|||||||
const FolderViewer = () => import ('/imports/ui/properties/viewers/FolderViewer.vue');
|
const FolderViewer = () => import ('/imports/ui/properties/viewers/FolderViewer.vue');
|
||||||
const ItemViewer = () => import ('/imports/ui/properties/viewers/ItemViewer.vue');
|
const ItemViewer = () => import ('/imports/ui/properties/viewers/ItemViewer.vue');
|
||||||
const NoteViewer = () => import ('/imports/ui/properties/viewers/NoteViewer.vue');
|
const NoteViewer = () => import ('/imports/ui/properties/viewers/NoteViewer.vue');
|
||||||
|
const PointBuyViewer = () => import ('/imports/ui/properties/viewers/PointBuyViewer.vue');
|
||||||
const ProficiencyViewer = () => import ('/imports/ui/properties/viewers/ProficiencyViewer.vue');
|
const ProficiencyViewer = () => import ('/imports/ui/properties/viewers/ProficiencyViewer.vue');
|
||||||
const ReferenceViewer = () => import ('/imports/ui/properties/viewers/ReferenceViewer.vue');
|
const ReferenceViewer = () => import ('/imports/ui/properties/viewers/ReferenceViewer.vue');
|
||||||
const RollViewer = () => import ('/imports/ui/properties/viewers/RollViewer.vue');
|
const RollViewer = () => import ('/imports/ui/properties/viewers/RollViewer.vue');
|
||||||
@@ -45,6 +46,7 @@ export default {
|
|||||||
folder: FolderViewer,
|
folder: FolderViewer,
|
||||||
item: ItemViewer,
|
item: ItemViewer,
|
||||||
note: NoteViewer,
|
note: NoteViewer,
|
||||||
|
pointBuy: PointBuyViewer,
|
||||||
proficiency: ProficiencyViewer,
|
proficiency: ProficiencyViewer,
|
||||||
propertySlot: SlotViewer,
|
propertySlot: SlotViewer,
|
||||||
roll: RollViewer,
|
roll: RollViewer,
|
||||||
|
|||||||
Reference in New Issue
Block a user