Migrating UI for new data structures
This commit is contained in:
@@ -47,17 +47,17 @@ const damageProperty = new ValidatedMethod({
|
||||
export function damagePropertyWork({property, operation, value}){
|
||||
let damage, newValue;
|
||||
if (operation === 'set'){
|
||||
const currentValue = property.value;
|
||||
const total = property.total;
|
||||
// Set represents what we want the value to be after damage
|
||||
// So we need the actual damage to get to that value
|
||||
damage = currentValue - value;
|
||||
damage = total - value;
|
||||
// Damage can't exceed total value
|
||||
if (damage > currentValue) damage = currentValue;
|
||||
if (damage > total) damage = total;
|
||||
// Damage must be positive
|
||||
if (damage < 0) damage = 0;
|
||||
newValue = property.total - damage;
|
||||
} else if (operation === 'increment'){
|
||||
let currentValue = property.value - (property.damage || 0);
|
||||
let currentValue = property.value;
|
||||
let currentDamage = property.damage;
|
||||
let increment = value;
|
||||
// Can't increase damage above the remaining value
|
||||
@@ -74,6 +74,7 @@ export function damagePropertyWork({property, operation, value}){
|
||||
}, {
|
||||
selector: property
|
||||
});
|
||||
return damage;
|
||||
}
|
||||
|
||||
export default damageProperty;
|
||||
|
||||
@@ -25,7 +25,6 @@ const dealDamage = new ValidatedMethod({
|
||||
// permissions
|
||||
let creature = Creatures.findOne(creatureId, {
|
||||
fields: {
|
||||
damageMultipliers: 1,
|
||||
owner: 1,
|
||||
readers: 1,
|
||||
writers: 1,
|
||||
@@ -33,37 +32,42 @@ const dealDamage = new ValidatedMethod({
|
||||
});
|
||||
assertEditPermission(creature, this.userId);
|
||||
|
||||
// Get all the health bars and do damage to them
|
||||
let healthBars = CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
type: 'attribute',
|
||||
attributeType:'healthBar',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: -1},
|
||||
});
|
||||
let multiplier = creature.damageMultipliers[damageType];
|
||||
if (multiplier === undefined) multiplier = 1;
|
||||
let totalDamage = Math.floor(amount * multiplier);
|
||||
let damageLeft = totalDamage;
|
||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||
let propertyIds = [];
|
||||
let propertiesDependedAponIds = [];
|
||||
healthBars.forEach(healthBar => {
|
||||
if (damageLeft === 0) return;
|
||||
let damageAdded = damagePropertyWork({
|
||||
property: healthBar,
|
||||
operation: 'increment',
|
||||
value: damageLeft,
|
||||
});
|
||||
damageLeft -= damageAdded;
|
||||
propertyIds.push(healthBar._id);
|
||||
propertiesDependedAponIds.push(...healthBar.dependencies);
|
||||
});
|
||||
const totalDamage = dealDamageWork({creature, damageType, amount})
|
||||
computeCreature(creatureId);
|
||||
return totalDamage;
|
||||
},
|
||||
});
|
||||
|
||||
export function dealDamageWork({creature, damageType, amount}){
|
||||
console.log({damageType, amount})
|
||||
// Get all the health bars and do damage to them
|
||||
let healthBars = CreatureProperties.find({
|
||||
'ancestors.id': creature._id,
|
||||
type: 'attribute',
|
||||
attributeType:'healthBar',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: -1},
|
||||
});
|
||||
//let multiplier = creature.damageMultipliers[damageType];
|
||||
//if (multiplier === undefined) multiplier = 1;
|
||||
//let totalDamage = Math.floor(amount * multiplier);
|
||||
const totalDamage = amount;
|
||||
let damageLeft = totalDamage;
|
||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||
let propertyIds = [];
|
||||
healthBars.forEach(healthBar => {
|
||||
if (damageLeft === 0) return;
|
||||
let damageAdded = damagePropertyWork({
|
||||
property: healthBar,
|
||||
operation: 'increment',
|
||||
value: damageLeft,
|
||||
});
|
||||
damageLeft -= damageAdded;
|
||||
propertyIds.push(healthBar._id);
|
||||
});
|
||||
return totalDamage;
|
||||
}
|
||||
|
||||
export default dealDamage;
|
||||
|
||||
@@ -10,10 +10,10 @@ export default function defaultCharacterProperties(creatureId){
|
||||
{
|
||||
type: 'propertySlot',
|
||||
name: 'Ruleset',
|
||||
description: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.',
|
||||
description: {text: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.'},
|
||||
slotTags: ['base'],
|
||||
tags: [],
|
||||
quantityExpected: 1,
|
||||
quantityExpected: {calculation: '1'},
|
||||
hideWhenFull: true,
|
||||
spaceLeft: 1,
|
||||
totalFilled: 0,
|
||||
|
||||
@@ -121,6 +121,7 @@ export function insertCreatureLogWork({log, creature, method}){
|
||||
if (typeof log === 'string'){
|
||||
log = {content: [{value: log}]};
|
||||
}
|
||||
if (!log.content?.length) return;
|
||||
log.date = new Date();
|
||||
// Insert it
|
||||
let id = CreatureLogs.insert(log);
|
||||
|
||||
@@ -3,6 +3,7 @@ import adjustment from './applyPropertyByType/applyAdjustment.js';
|
||||
import branch from './applyPropertyByType/applyBranch.js';
|
||||
import buff from './applyPropertyByType/applyBuff.js';
|
||||
import damage from './applyPropertyByType/applyDamage.js';
|
||||
import note from './applyPropertyByType/applyNote.js';
|
||||
import roll from './applyPropertyByType/applyRoll.js';
|
||||
import savingThrow from './applyPropertyByType/applySavingThrow.js';
|
||||
import toggle from './applyPropertyByType/applyToggle.js';
|
||||
@@ -13,6 +14,7 @@ const applyPropertyByType = {
|
||||
branch,
|
||||
buff,
|
||||
damage,
|
||||
note,
|
||||
roll,
|
||||
savingThrow,
|
||||
spell: action,
|
||||
|
||||
@@ -9,15 +9,22 @@ import { damagePropertyWork } from '/imports/api/creature/creatureProperties/met
|
||||
export default function applyAction(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
if (prop.target === 'self') targets = [creature];
|
||||
|
||||
// Log the name and description
|
||||
let content = { name: prop.name };
|
||||
if (prop.description?.text){
|
||||
recalculateInlineCalculations(prop.description, scope, log);
|
||||
content.value = prop.description.value;
|
||||
}
|
||||
if (content.name || content.value){
|
||||
log.content.push(content);
|
||||
}
|
||||
|
||||
// Spend the resources
|
||||
const failed = spendResources({prop, log, scope});
|
||||
if (failed) return;
|
||||
|
||||
let content = { name: prop.name };
|
||||
if (prop.summary?.text){
|
||||
recalculateInlineCalculations(prop.summary, scope, log);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
log.content.push(content);
|
||||
// Attack if there is an attack roll
|
||||
if (prop.attackRoll && prop.attackRoll.calculation){
|
||||
if (targets.length){
|
||||
targets.forEach(target => {
|
||||
@@ -29,6 +36,8 @@ export default function applyAction(node, {creature, targets, scope, log}){
|
||||
applyAttackWithoutTarget({prop, scope, log});
|
||||
applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
} else {
|
||||
applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,18 +48,18 @@ function applyAttackWithoutTarget({prop, scope, log}){
|
||||
delete scope['$criticalMiss'];
|
||||
delete scope['$attackRoll'];
|
||||
|
||||
recalculateCalculation(prop.rollBonus, scope, log);
|
||||
recalculateCalculation(prop.attackRoll, scope, log);
|
||||
|
||||
let value = rollDice(1, 20)[0];
|
||||
scope['$attackRoll'] = {value};
|
||||
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||
let result = value + prop.rollBonus.value;
|
||||
let result = value + prop.attackRoll.value;
|
||||
scope['$toHit'] = {value: result};
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : 'To Hit',
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
value: `1d20 [${value}] + ${prop.attackRoll.value} = ` + result,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,7 +71,7 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
delete scope['$attackDiceRoll'];
|
||||
delete scope['$attackRoll'];
|
||||
|
||||
recalculateCalculation(prop.rollBonus, scope, log);
|
||||
recalculateCalculation(prop.attackRoll, scope, log);
|
||||
|
||||
const value = rollDice(1, 20)[0];
|
||||
scope['$attackDiceRoll'] = {value};
|
||||
@@ -71,7 +80,7 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
const criticalMiss = value === 1;
|
||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||
if (criticalMiss) scope['$criticalMiss'] = {value: true};
|
||||
const result = value + prop.rollBonus.value;
|
||||
const result = value + prop.attackRoll.value;
|
||||
scope['$attackRoll'] = {value: result};
|
||||
if (target.variables.armor){
|
||||
const armor = target.variables.armor.value;
|
||||
@@ -81,7 +90,7 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
'Miss!'
|
||||
log.content.push({
|
||||
name,
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
|
||||
});
|
||||
if ((result > armor) || (criticalHit)){
|
||||
scope['$attackHit'] = true;
|
||||
@@ -95,7 +104,7 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
});
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit',
|
||||
value: `1d20 {${value}} + ${prop.rollBonus.value} = ` + result,
|
||||
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -106,7 +115,7 @@ function applyChildren(node, args){
|
||||
|
||||
function spendResources({prop, log, scope}){
|
||||
// Check Uses
|
||||
if (prop.usesUsed >= prop.uses?.value){
|
||||
if (prop.usesLeft < 0){
|
||||
log.content.push({
|
||||
name: 'Error',
|
||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||
@@ -127,6 +136,7 @@ function spendResources({prop, log, scope}){
|
||||
let gainLog = [];
|
||||
try {
|
||||
prop.resources.itemsConsumed.forEach(itemConsumed => {
|
||||
recalculateCalculation(itemConsumed.quantity, scope, log);
|
||||
if (!itemConsumed.itemId){
|
||||
throw 'No ammo was selected for this prop';
|
||||
}
|
||||
@@ -166,7 +176,7 @@ function spendResources({prop, log, scope}){
|
||||
itemQuantityAdjustments.forEach(adjustQuantityWork);
|
||||
|
||||
// Use uses
|
||||
if (prop.usesResult){
|
||||
if (prop.usesLeft){
|
||||
CreatureProperties.update(prop._id, {
|
||||
$inc: {usesUsed: 1}
|
||||
}, {
|
||||
@@ -174,24 +184,29 @@ function spendResources({prop, log, scope}){
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Uses left',
|
||||
value: prop.usesResult - (prop.usesUsed || 0) - 1,
|
||||
value: prop.usesLeft - (prop.usesUsed || 0) - 1,
|
||||
});
|
||||
}
|
||||
|
||||
// Damage stats
|
||||
prop.resources.attributesConsumed.forEach(attConsumed => {
|
||||
if (!attConsumed.quantity) return;
|
||||
recalculateCalculation(attConsumed.quantity, scope, log);
|
||||
|
||||
if (!attConsumed.quantity?.value) return;
|
||||
let stat = scope[attConsumed.variableName];
|
||||
if (!stat) return;
|
||||
if (!stat){
|
||||
spendLog.push(stat.name + ': ' + ' not found');
|
||||
return;
|
||||
}
|
||||
damagePropertyWork({
|
||||
property: stat,
|
||||
operation: 'increment',
|
||||
value: attConsumed.quantity,
|
||||
value: attConsumed.quantity.value,
|
||||
});
|
||||
if (attConsumed.quantity > 0){
|
||||
spendLog.push(stat.name + ': ' + attConsumed.quantity);
|
||||
} else if (attConsumed.quantity < 0){
|
||||
gainLog.push(stat.name + ': ' + -attConsumed.quantity);
|
||||
if (attConsumed.quantity.value > 0){
|
||||
spendLog.push(stat.name + ': ' + attConsumed.quantity.value);
|
||||
} else if (attConsumed.quantity.value < 0){
|
||||
gainLog.push(stat.name + ': ' + -attConsumed.quantity.value);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -14,12 +14,8 @@ export default function applyAdjustment(node, {
|
||||
|
||||
// Evaluate the amount
|
||||
recalculateCalculation(prop.amount, scope, log);
|
||||
prop.amount.errors?.forEach(error => {
|
||||
if (error.type !== 'info'){
|
||||
log.content.push({name: 'Error', value: error.message});
|
||||
}
|
||||
});
|
||||
const value = prop.amount.value;
|
||||
|
||||
const value = +prop.amount.value;
|
||||
if (!isFinite(value)) {
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
@@ -32,12 +28,12 @@ export default function applyAdjustment(node, {
|
||||
name: 'Error',
|
||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||
});
|
||||
return;
|
||||
return applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
damagePropertyWork({
|
||||
property: stat,
|
||||
operation: prop.operation,
|
||||
value,
|
||||
value: value,
|
||||
});
|
||||
log.content.push({
|
||||
name: 'Attribute damage',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js';
|
||||
import { dealDamageWork } from '/imports/api/creature/creatureProperties/methods/dealDamage.js';
|
||||
import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import { Context } from '/imports/parser/resolve.js';
|
||||
import resolve, { Context, toString } from '/imports/parser/resolve.js';
|
||||
import logErrors from './shared/logErrors.js';
|
||||
|
||||
export default function applyDamage(node, {
|
||||
creature, targets, scope, log
|
||||
@@ -14,6 +14,12 @@ export default function applyDamage(node, {
|
||||
};
|
||||
|
||||
const prop = node.node;
|
||||
|
||||
// Skip if there is no parse node to work with
|
||||
if (!prop.amount.parseNode) return;
|
||||
|
||||
// Choose target
|
||||
|
||||
let damageTargets = prop.target === 'self' ? [creature] : targets;
|
||||
// Determine if the hit is critical
|
||||
let criticalHit = scope['$criticalHit']?.value &&
|
||||
@@ -23,40 +29,66 @@ export default function applyDamage(node, {
|
||||
let context = new Context({
|
||||
options: {doubleRolls: criticalHit},
|
||||
});
|
||||
recalculateCalculation(prop.amount, scope, log, context);
|
||||
|
||||
// If we didn't end up with a finite amount, give up
|
||||
if (!isFinite(prop.amount?.value)) return applyChildren();
|
||||
// Gather all the lines we need to log into an array
|
||||
const logValue = [];
|
||||
const logName = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
|
||||
// Compile the dice roll and store that string first
|
||||
const {result: compiled} = resolve('compiled', prop.amount.parseNode, scope, context);
|
||||
logValue.push(toString(compiled));
|
||||
logErrors(context.errors, log);
|
||||
|
||||
// roll the dice only and store that string
|
||||
const {result: rolled} = resolve('roll', prop.amount.parseNode, scope, context);
|
||||
logValue.push(toString(rolled));
|
||||
logErrors(context.errors, log);
|
||||
|
||||
// Reset the errors so we don't log the same errors twice
|
||||
context.errors = [];
|
||||
|
||||
// Resolve the roll to a final value
|
||||
const {result: reduced} = resolve('reduce', rolled, scope, context);
|
||||
logErrors(context.errors, log);
|
||||
|
||||
// Store the result
|
||||
if (reduced.parseType === 'constant'){
|
||||
prop.amount.value = reduced.value;
|
||||
} else if (reduced.parseType === 'error'){
|
||||
prop.amount.value = null;
|
||||
} else {
|
||||
prop.amount.value = toString(reduced);
|
||||
}
|
||||
|
||||
const damage = +reduced.value;
|
||||
|
||||
// If we didn't end up with a constant of finite amount, give up
|
||||
if (reduced?.parseType !== 'constant' && !isFinite(reduced.value)){
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
// Memoise the damage suffix for the log
|
||||
let suffix = (criticalHit ? ' critical ' : ' ') +
|
||||
prop.damageType +
|
||||
(prop.damageType !== ' healing ' ? ' damage ': '');
|
||||
(prop.damageType !== 'healing' ? ' damage ': '');
|
||||
|
||||
if (damageTargets && damageTargets.length) {
|
||||
// Iterate through all the targets
|
||||
damageTargets.forEach(target => {
|
||||
let name = prop.damageType === 'healing' ? 'Healing' : 'Damage';
|
||||
|
||||
// Deal the damage to the target
|
||||
let damageDealt = dealDamage.call({
|
||||
creatureId: target._id,
|
||||
let damageDealt = dealDamageWork({
|
||||
creature: target,
|
||||
damageType: prop.damageType,
|
||||
amount: prop.amount.value,
|
||||
amount: damage,
|
||||
});
|
||||
|
||||
// Log the damage done
|
||||
if (target._id === creature._id){
|
||||
// Target is same as self, log damage as such
|
||||
log.content.push({
|
||||
name,
|
||||
value: damageDealt + suffix + ' to self',
|
||||
});
|
||||
logValue.push(damageDealt + suffix + ' to self');
|
||||
} else {
|
||||
log.content.push({
|
||||
name,
|
||||
value: 'Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`,
|
||||
});
|
||||
logValue.push('Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`);
|
||||
// Log the damage received on that creature's log as well
|
||||
insertCreatureLog.call({
|
||||
log: {
|
||||
@@ -71,10 +103,11 @@ export default function applyDamage(node, {
|
||||
});
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
log.content.push({
|
||||
name: prop.damageType === 'healing' ? 'Healing' : 'Damage',
|
||||
value: prop.amount.value + suffix,
|
||||
});
|
||||
logValue.push(damage + suffix);
|
||||
}
|
||||
log.content.push({
|
||||
name: logName,
|
||||
value: logValue.join('\n'),
|
||||
});
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import recalculateInlineCalculations from './shared/recalculateInlineCalculations.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
|
||||
export default function applyNote(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
|
||||
// Log Name, summary
|
||||
let content = { name: prop.name };
|
||||
if (prop.summary?.text){
|
||||
recalculateInlineCalculations(prop.summary, scope, log);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
if (content.name || content.value){
|
||||
log.content.push(content);
|
||||
}
|
||||
// Log description
|
||||
if (prop.description?.text){
|
||||
recalculateInlineCalculations(prop.description, scope, log);
|
||||
log.content.push({value: prop.description.value});
|
||||
}
|
||||
// Apply children
|
||||
node.children.forEach(child => applyProperty(child, {
|
||||
creature, targets, scope, log
|
||||
}));
|
||||
}
|
||||
@@ -5,14 +5,14 @@ export default function applyRoll(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
|
||||
if (prop.roll?.calculation){
|
||||
recalculateCalculation(prop.roll, scope, log, context);
|
||||
recalculateCalculation(prop.roll, scope, log);
|
||||
|
||||
if (isFinite(prop.roll.value)){
|
||||
scope[prop.variableName] = prop.roll.value;
|
||||
}
|
||||
log.content.push({
|
||||
name: prop.name,
|
||||
value: prop.variableName + ' = ' + prop.roll + ' = ' + prop.roll.value,
|
||||
value: prop.variableName + ' = ' + prop.roll.calculation + ' = ' + prop.roll.value,
|
||||
});
|
||||
}
|
||||
return node.children.forEach(child => applyProperty(child, {
|
||||
|
||||
@@ -3,11 +3,11 @@ import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
|
||||
export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||
let saveTargets = prop.target === 'self' ? [creature] : targets;
|
||||
|
||||
const prop = node.node;
|
||||
|
||||
recalculateCalculation(prop.dc, scope, log, context);
|
||||
let saveTargets = prop.target === 'self' ? [creature] : targets;
|
||||
|
||||
recalculateCalculation(prop.dc, scope, log);
|
||||
|
||||
const dc = (prop.dc?.value);
|
||||
if (!isFinite(dc)){
|
||||
|
||||
@@ -2,7 +2,7 @@ import evaluateCalculation from '/imports/api/engine/computation/utility/evaluat
|
||||
import logErrors from './logErrors.js';
|
||||
|
||||
export default function recalculateCalculation(calc, scope, log, context){
|
||||
if (!calc.parseNode) return;
|
||||
if (!calc?.parseNode) return;
|
||||
calc._parseLevel = 'reduce';
|
||||
evaluateCalculation(calc, scope, context);
|
||||
logErrors(calc.errors, log);
|
||||
|
||||
@@ -72,9 +72,9 @@ const doAction = new ValidatedMethod({
|
||||
doActionWork({creature, targets, properties, ancestors, method: this});
|
||||
|
||||
// Recompute all involved creatures
|
||||
Meteor.defer(() => computeCreature(creature._id));
|
||||
computeCreature(creature._id);
|
||||
targets.forEach(target => {
|
||||
Meteor.defer(() => computeCreature(target._id));
|
||||
computeCreature(target._id);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
11
app/imports/api/engine/actions/doAction.test.js
Normal file
11
app/imports/api/engine/actions/doAction.test.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import '/imports/api/simpleSchemaConfig.js';
|
||||
//import testTypes from './testTypes/index.js';
|
||||
import { doActionWork } from './doAction.js';
|
||||
import createAction from './tests/createAction.testFn.js';
|
||||
|
||||
describe('Do Action', function(){
|
||||
it('Does an empty action', function(){
|
||||
doActionWork(createAction({properties: [{type: 'action'}]}));
|
||||
});
|
||||
//testTypes.forEach(test => it(test.text, test.fn));
|
||||
});
|
||||
26
app/imports/api/engine/actions/tests/createAction.testFn.js
Normal file
26
app/imports/api/engine/actions/tests/createAction.testFn.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
|
||||
export default function createAction({
|
||||
creature = {_id: 'creatureId'},
|
||||
targets = [],
|
||||
properties = [],
|
||||
ancestors = [],
|
||||
method
|
||||
} = {}){
|
||||
properties = properties.map(cleanProp);
|
||||
ancestors = ancestors.map(cleanProp);
|
||||
creature = cleanCreature(creature);
|
||||
ancestors = ancestors.map(cleanCreature);
|
||||
return {creature, targets, properties, ancestors, method};
|
||||
}
|
||||
|
||||
function cleanProp(prop){
|
||||
let schema = CreatureProperties.simpleSchema(prop);
|
||||
return schema.clean(prop);
|
||||
}
|
||||
|
||||
function cleanCreature(creature){
|
||||
let schema = Creatures.simpleSchema(creature);
|
||||
return schema.clean(creature);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import applyAction from './applyAction.testFn.js';
|
||||
|
||||
export default [{
|
||||
text: 'Applies actions',
|
||||
fn: applyAction,
|
||||
},];
|
||||
@@ -3,9 +3,9 @@ import walkDown from '/imports/api/engine/computation/utility/walkdown.js';
|
||||
export default function computeInactiveStatus(node){
|
||||
const prop = node.node;
|
||||
if (isActive(prop)) return;
|
||||
// Unequipped items disable their children, but are not disabled themselves
|
||||
// All notes do the same
|
||||
if (prop.type !== 'item' && prop.type !== 'note' ){
|
||||
// Unequipped items, notes, and actions disable their children,
|
||||
// but are not disabled themselves
|
||||
if (prop.type !== 'item' && prop.type !== 'note' && prop.type !== 'action' ){
|
||||
prop.inactive = true;
|
||||
prop.deactivatedBySelf = true;
|
||||
}
|
||||
@@ -23,6 +23,7 @@ function isActive(prop){
|
||||
case 'item': return !!prop.equipped;
|
||||
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared;
|
||||
case 'note': return false;
|
||||
case 'action': return false;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import applyFnToKey from '/imports/api/engine/computation/utility/applyFnToKey.js';
|
||||
import { get } from 'lodash';
|
||||
import { get, unset } from 'lodash';
|
||||
import errorNode from '/imports/parser/parseTree/error.js';
|
||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||
|
||||
@@ -53,6 +53,11 @@ function parseAllCalculationFields(prop, schemas){
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
if (!calcObj) return;
|
||||
// Delete the whole calculation object if the calculation string isn't set
|
||||
if (!calcObj.calculation){
|
||||
unset(prop, calcKey);
|
||||
return;
|
||||
}
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
// Store the level to compute down to later
|
||||
@@ -64,12 +69,6 @@ function parseAllCalculationFields(prop, schemas){
|
||||
}
|
||||
|
||||
function parseCalculation(calcObj){
|
||||
// If there is no calculation clear the cached parse node and error
|
||||
if (!calcObj.calculation){
|
||||
delete calcObj.hash;
|
||||
delete calcObj.parseError;
|
||||
return;
|
||||
}
|
||||
const calcHash = cyrb53(calcObj.calculation);
|
||||
// If the cached parse calculation is equal to the calculation, skip
|
||||
if (calcHash === calcObj.hash){
|
||||
|
||||
@@ -2,6 +2,9 @@ export default function computeAction(computation, node){
|
||||
const prop = node.data;
|
||||
if (prop.uses){
|
||||
prop.usesLeft = prop.uses.value - (prop.usesUsed || 0);
|
||||
if (!prop.usesLeft){
|
||||
prop.insufficientResources = true;
|
||||
}
|
||||
}
|
||||
computeResources(computation, node);
|
||||
if (!prop.resources) return;
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function(){
|
||||
assert.equal(prop.usesLeft, 2);
|
||||
|
||||
const rolled = computation.propsById['rolledDescriptionId'];
|
||||
assert.equal(rolled.summary.value, 'test roll gets compiled 1d4 + 4 properly');
|
||||
assert.equal(rolled.summary.value, 'test roll gets compiled d4 + 4 properly');
|
||||
|
||||
const itemConsumed = prop.resources.itemsConsumed[0];
|
||||
assert.equal(itemConsumed.quantity.value, 3);
|
||||
@@ -26,7 +26,6 @@ export default function(){
|
||||
const attConsumed = prop.resources.attributesConsumed[0];
|
||||
assert.equal(attConsumed.quantity.value, 4);
|
||||
assert.equal(attConsumed.available, 9);
|
||||
assert.equal(attConsumed.statId, 'resourceVarId');
|
||||
assert.equal(attConsumed.statName, 'Resource Var');
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function migrateProperty({collection, reversed, prop}){
|
||||
{from: 'dependencies'}
|
||||
];
|
||||
let migratedProp = transformFields(prop, transforms, reversed);
|
||||
const schema = collection.simpleSchema({type: prop.type});
|
||||
const schema = collection.simpleSchema({type: migratedProp.type});
|
||||
// Only clean if the schema version matches our destination version
|
||||
if(!reversed && SCHEMA_VERSION === 1){
|
||||
try {
|
||||
@@ -80,7 +80,9 @@ const transformsByPropType = {
|
||||
{from: 'type', to: 'type', up: () => 'action'},
|
||||
],
|
||||
'attribute': [
|
||||
...getComputedPropertyTransforms('baseValue'),
|
||||
{from: 'baseValueCalculation', to: 'baseValue.calculation'},
|
||||
{from: 'baseValue', to: 'baseValue.value', up: nanToNull},
|
||||
{from: 'baseValueErrors', to: 'baseValue.errors', up: trimErrors},
|
||||
...getComputedPropertyTransforms('spellSlotLevel'),
|
||||
...getInlineComputationTransforms('description'),
|
||||
{from: 'value', to: 'total', up: nanToNull},
|
||||
|
||||
@@ -13,7 +13,6 @@ const exampleAction = {
|
||||
'_id':'FaK6jXEj3pSe7mNuu',
|
||||
'quantity': '1',
|
||||
'variableName':'HunterTech',
|
||||
'statId':'qccf9j5tfNJjZ3GGn',
|
||||
'statName':'Hunter\'s Technique',
|
||||
'available':5
|
||||
}],
|
||||
|
||||
@@ -22,7 +22,7 @@ const rollNode = {
|
||||
},
|
||||
toString(node){
|
||||
if (
|
||||
node.left.parseType === 'number' && node.left.value === 1
|
||||
node.left.valueType === 'number' && node.left.value === 1
|
||||
){
|
||||
return `d${toString(node.right)}`;
|
||||
} else {
|
||||
@@ -32,10 +32,10 @@ const rollNode = {
|
||||
roll(node, scope, context){
|
||||
const {result: left} = resolve('reduce', node.left, scope, context);
|
||||
const {result: right} = resolve('reduce', node.right, scope, context);
|
||||
if (left.parseType !== 'number' && !Number.isInteger(left.value)){
|
||||
if (left.valueType !== 'number' && !Number.isInteger(left.value)){
|
||||
return errorResult('Number of dice is not an integer', node, context);
|
||||
}
|
||||
if (!right.isInteger){
|
||||
if (right.valueType !== 'number' && !Number.isInteger(right.value)){
|
||||
return errorResult('Dice size is not an integer', node, context);
|
||||
}
|
||||
let number = left.value;
|
||||
@@ -49,7 +49,7 @@ const rollNode = {
|
||||
let diceSize = right.value;
|
||||
let values = rollDice(number, diceSize);
|
||||
if (context){
|
||||
context.storeRoll({number, diceSize, values});
|
||||
context.rolls.push({number, diceSize, values});
|
||||
}
|
||||
return {
|
||||
result: rollArray.create({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import constant from './constant.js';
|
||||
|
||||
const rollArray = {
|
||||
constructor({values, diceSize, diceNum}) {
|
||||
create({values, diceSize, diceNum}) {
|
||||
return {
|
||||
parseType: 'rollArray',
|
||||
values,
|
||||
|
||||
@@ -35,10 +35,10 @@
|
||||
{{ typeName || 'Type' }}
|
||||
</v-tab>
|
||||
<v-tab :disabled="!type">
|
||||
Library
|
||||
Create
|
||||
</v-tab>
|
||||
<v-tab :disabled="!type">
|
||||
Create
|
||||
Library
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-tabs-items
|
||||
@@ -52,6 +52,22 @@
|
||||
@select="e => type = e"
|
||||
/>
|
||||
</v-tab-item>
|
||||
<v-tab-item :disabled="!type">
|
||||
<v-card-text
|
||||
v-if="!$slots['unwrapped-content']"
|
||||
>
|
||||
<component
|
||||
:is="type"
|
||||
v-if="type"
|
||||
class="creature-property-form"
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
@change="change"
|
||||
@push="push"
|
||||
@pull="pull"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-tab-item>
|
||||
<v-tab-item :disabled="!type">
|
||||
<v-expansion-panels
|
||||
multiple
|
||||
@@ -119,22 +135,6 @@
|
||||
</v-fade-transition>
|
||||
</v-layout>
|
||||
</v-tab-item>
|
||||
<v-tab-item :disabled="!type">
|
||||
<v-card-text
|
||||
v-if="!$slots['unwrapped-content']"
|
||||
>
|
||||
<component
|
||||
:is="type"
|
||||
v-if="type"
|
||||
class="creature-property-form"
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
@change="change"
|
||||
@push="push"
|
||||
@pull="pull"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
<template slot="actions">
|
||||
<v-btn
|
||||
@@ -145,7 +145,7 @@
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="tab === 2"
|
||||
v-if="tab === 1"
|
||||
text
|
||||
color="primary"
|
||||
:disabled="!valid"
|
||||
@@ -154,7 +154,7 @@
|
||||
create
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-else-if="tab === 1"
|
||||
v-else-if="tab === 2"
|
||||
text
|
||||
color="primary"
|
||||
:disabled="!selectedNodeIds.length"
|
||||
@@ -275,9 +275,9 @@
|
||||
this._subs.searchLibraryNodes.setData('type', type);
|
||||
if (!type) return;
|
||||
if (SKIP_LIBRARY_PROP_TYPES.includes(type)){
|
||||
this.tab = 2;
|
||||
} else {
|
||||
this.tab = 1;
|
||||
} else {
|
||||
this.tab = 2;
|
||||
}
|
||||
this.schema = propertySchemasIndex[type];
|
||||
this.validationContext = this.schema.newContext();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<v-btn
|
||||
icon
|
||||
outlined
|
||||
style="font-size: 18px;"
|
||||
style="font-size: 16px; letter-spacing: normal;"
|
||||
class="mr-2"
|
||||
:color="model.color || 'primary'"
|
||||
:loading="doActionLoading"
|
||||
@@ -65,7 +65,7 @@
|
||||
:action="model"
|
||||
/>
|
||||
<v-divider
|
||||
v-if="model.summary || children.length"
|
||||
v-if="model.summary"
|
||||
class="my-2"
|
||||
/>
|
||||
</template>
|
||||
@@ -73,17 +73,7 @@
|
||||
<property-description
|
||||
:model="model.summary"
|
||||
/>
|
||||
<v-divider
|
||||
v-if="children.length"
|
||||
class="my-2"
|
||||
/>
|
||||
</template>
|
||||
<tree-node-view
|
||||
v-for="child in children"
|
||||
:key="child._id"
|
||||
class="action-child"
|
||||
:model="child"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
@@ -91,9 +81,7 @@
|
||||
<script lang="js">
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
//import doAction from '/imports/api/creature/actions/doAction.js';
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import doAction from '/imports/api/engine/actions/doAction.js';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue';
|
||||
@@ -101,7 +89,6 @@ import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TreeNodeView,
|
||||
AttributeConsumedView,
|
||||
ItemConsumedView,
|
||||
PropertyDescription,
|
||||
@@ -130,8 +117,8 @@ export default {
|
||||
}},
|
||||
computed: {
|
||||
rollBonus(){
|
||||
if (!this.model.rollBonus) return;
|
||||
return numberToSignedString(this.model.rollBonus.value);
|
||||
if (!this.model.attackRoll) return;
|
||||
return numberToSignedString(this.model.attackRoll.value);
|
||||
},
|
||||
rollBonusTooLong(){
|
||||
return this.rollBonus && this.rollBonus.length > 3;
|
||||
@@ -152,17 +139,6 @@ export default {
|
||||
return `$vuetify.icons.${this.model.actionType}`;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
children(){
|
||||
return CreatureProperties.find({
|
||||
'parent.id': this.model._id,
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
|
||||
@@ -1,42 +1,68 @@
|
||||
<template lang="html">
|
||||
<div class="action-form">
|
||||
<div class="layout column align-center">
|
||||
<icon-picker
|
||||
label="Icon"
|
||||
:value="model.icon"
|
||||
:error-messages="errors.icon"
|
||||
@change="change('icon', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<smart-select
|
||||
label="Action type"
|
||||
:items="actionTypes"
|
||||
:value="model.actionType"
|
||||
:error-messages="errors.actionType"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
:hint="actionTypeHints[model.actionType]"
|
||||
@change="change('actionType', ...arguments)"
|
||||
/>
|
||||
<v-row
|
||||
justify="center"
|
||||
class="mb-3"
|
||||
>
|
||||
<v-col cols="1">
|
||||
<icon-color-menu
|
||||
:model="model"
|
||||
:errors="errors"
|
||||
@change="e => $emit('change', e)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<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="Action type"
|
||||
:items="actionTypes"
|
||||
:value="model.actionType"
|
||||
:error-messages="errors.actionType"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
:hint="actionTypeHints[model.actionType]"
|
||||
@change="change('actionType', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<computed-field
|
||||
label="Attack Roll Bonus"
|
||||
hint="The bonus to attack if this action has an attack roll"
|
||||
:model="model.rollBonus"
|
||||
:error-messages="errors.rollBonus"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['rollBonus', ...path], value, ack})"
|
||||
/>
|
||||
<v-slide-x-transition mode="out-in">
|
||||
<v-switch
|
||||
v-if="!isAttack"
|
||||
label="Attack roll"
|
||||
:value="attackSwitch"
|
||||
@change="e => attackSwitch = e"
|
||||
/>
|
||||
<computed-field
|
||||
v-else
|
||||
label="To Hit"
|
||||
prefix="1d20 + "
|
||||
hint="The bonus to attack if this action has an attack roll"
|
||||
:model="model.attackRoll"
|
||||
:error-messages="errors.attackRoll"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['attackRoll', ...path], value, ack})"
|
||||
/>
|
||||
</v-slide-x-transition>
|
||||
|
||||
<inline-computation-field
|
||||
label="Summary"
|
||||
hint="This will appear in the action card in the character sheet"
|
||||
hint="This will appear in the action card in the character sheet, summarise what the action does"
|
||||
:model="model.summary"
|
||||
:error-messages="errors.summary"
|
||||
@change="({path, value, ack}) =>
|
||||
@@ -45,7 +71,7 @@
|
||||
|
||||
<inline-computation-field
|
||||
label="Description"
|
||||
hint="The rest of the description that doesn't fit in the summary goes here"
|
||||
hint="This text will be displayed in the log when the action is taken"
|
||||
:model="model.description"
|
||||
:error-messages="errors.description"
|
||||
@change="({path, value, ack}) =>
|
||||
@@ -82,25 +108,36 @@
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
-->
|
||||
<div class="layout wrap">
|
||||
<computed-field
|
||||
label="Uses"
|
||||
hint="How many times this action can be used before needing to be reset"
|
||||
:model="model.uses"
|
||||
:error-messages="errors.uses"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['uses', ...path], value, ack})"
|
||||
/>
|
||||
<text-field
|
||||
label="Uses used"
|
||||
type="number"
|
||||
hint="How many times this action has already been used: should be 0 in most cases"
|
||||
style="flex-basis: 300px;"
|
||||
:value="model.usesUsed"
|
||||
:error-messages="errors.uses"
|
||||
@change="change('usesUsed', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<computed-field
|
||||
label="Uses"
|
||||
hint="How many times this action can be used before needing to be reset"
|
||||
class="mr-2"
|
||||
:model="model.uses"
|
||||
:error-messages="errors.uses"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['uses', ...path], value, ack})"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Uses used"
|
||||
type="number"
|
||||
hint="How many times this action has already been used: should be 0 in most cases"
|
||||
style="flex-basis: 300px;"
|
||||
:value="model.usesUsed"
|
||||
:error-messages="errors.uses"
|
||||
@change="change('usesUsed', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<smart-select
|
||||
label="Reset"
|
||||
clearable
|
||||
@@ -121,12 +158,14 @@
|
||||
import FormSection, {FormSections} from '/imports/ui/properties/forms/shared/FormSection.vue';
|
||||
import ResourcesForm from '/imports/ui/properties/forms/ResourcesForm.vue';
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import IconColorMenu from '/imports/ui/properties/forms/shared/IconColorMenu.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FormSection,
|
||||
FormSections,
|
||||
ResourcesForm,
|
||||
IconColorMenu,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
data(){
|
||||
@@ -176,6 +215,7 @@
|
||||
value: 'longRest',
|
||||
}
|
||||
],
|
||||
attackSwitch: false,
|
||||
};
|
||||
data.actionTypeHints = {};
|
||||
data.actionTypes.forEach(type => {
|
||||
@@ -183,6 +223,11 @@
|
||||
});
|
||||
return data;
|
||||
},
|
||||
computed: {
|
||||
isAttack(){
|
||||
return this.attackSwitch || !!this.model.attackRoll?.calculation
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
<template lang="html">
|
||||
<div class="adjustment-form">
|
||||
<div class="layout">
|
||||
<smart-combobox
|
||||
label="Attribute"
|
||||
hint="The attribute this adjustment will apply to"
|
||||
style="flex-basis: 300px;"
|
||||
:items="attributeList"
|
||||
:value="model.stat"
|
||||
:error-messages="errors.stat"
|
||||
@change="change('stat', ...arguments)"
|
||||
/>
|
||||
<computed-field
|
||||
label="Amount"
|
||||
hint="The amount of damage to apply to the selected stat, can be a calculation or roll. Negative values will restore the selected from previous damage. If the operation is set, this is the final value of the stat instead."
|
||||
style="flex-basis: 300px;"
|
||||
:model="model.amount"
|
||||
:error-messages="errors.amount"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['amount', ...path], value, ack})"
|
||||
/>
|
||||
</div>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<smart-combobox
|
||||
label="Attribute"
|
||||
hint="The attribute this adjustment will apply to"
|
||||
style="flex-basis: 300px;"
|
||||
:items="attributeList"
|
||||
:value="model.stat"
|
||||
:error-messages="errors.stat"
|
||||
@change="change('stat', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<computed-field
|
||||
label="Amount"
|
||||
:hint="model.operation === 'set' ? setHint : damageHint"
|
||||
style="flex-basis: 300px;"
|
||||
:model="model.amount"
|
||||
:error-messages="errors.amount"
|
||||
@change="({path, value, ack}) =>
|
||||
$emit('change', {path: ['amount', ...path], value, ack})"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<smart-select
|
||||
label="Operation"
|
||||
class="mx-1"
|
||||
@@ -69,6 +79,8 @@ export default {
|
||||
{text: 'Damage', value: 'increment'},
|
||||
{text: 'Set', value: 'set'},
|
||||
],
|
||||
damageHint: 'The amount of damage to apply to the selected stat, can be a calculation or roll. Negative values will restore the selected from previous damage. If the operation is set, this is the final value of the stat instead.',
|
||||
setHint: 'The value of the stat after applying this adjustment. The stat\'s value can\'t exceed its total',
|
||||
}},
|
||||
computed: {
|
||||
targetOptions(){
|
||||
@@ -88,11 +100,8 @@ export default {
|
||||
text: 'Self',
|
||||
value: 'self',
|
||||
}, {
|
||||
text: 'Roll once for each target',
|
||||
value: 'each',
|
||||
}, {
|
||||
text: 'Roll once and apply to every target',
|
||||
value: 'every',
|
||||
text: 'Target',
|
||||
value: 'target',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -13,22 +13,31 @@
|
||||
$emit('change', {path: ['baseValue', ...path], value, ack})"
|
||||
/>
|
||||
</div>
|
||||
<div class="layout wrap">
|
||||
<text-field
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<text-field
|
||||
label="Variable name"
|
||||
:value="model.variableName"
|
||||
style="flex-basis: 300px;"
|
||||
hint="Use this name in calculations to reference this attribute"
|
||||
:error-messages="errors.variableName"
|
||||
@change="change('variableName', ...arguments)"
|
||||
/>
|
||||
</div>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
label="Variable name"
|
||||
:value="model.variableName"
|
||||
hint="Use this name in calculations to reference this attribute"
|
||||
:error-messages="errors.variableName"
|
||||
@change="change('variableName', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<smart-select
|
||||
label="Type"
|
||||
:items="attributeTypes"
|
||||
|
||||
68
app/imports/ui/properties/forms/shared/IconColorMenu.vue
Normal file
68
app/imports/ui/properties/forms/shared/IconColorMenu.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template lang="html">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-badge
|
||||
icon="mdi-pencil"
|
||||
overlap
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
:color="model.color"
|
||||
outlined
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<property-icon
|
||||
:model="model"
|
||||
:color="model.color"
|
||||
/>
|
||||
</v-btn>
|
||||
</v-badge>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
<icon-picker
|
||||
label="Icon"
|
||||
:value="model.icon"
|
||||
:error-messages="errors.icon"
|
||||
@change="(value, ack) =>$emit('change', {path: ['icon'], value, ack})"
|
||||
/>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
<color-picker
|
||||
:value="model.color"
|
||||
@input="value =>$emit('change', {path: ['color'], value})"
|
||||
/>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PropertyIcon,
|
||||
ColorPicker,
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
errors: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -4,14 +4,23 @@
|
||||
*/
|
||||
import { get, toPath } from 'lodash';
|
||||
|
||||
function resolvePath(model, path){
|
||||
function resolvePath(model, path, set){
|
||||
let arrayPath = toPath(path);
|
||||
if (arrayPath.length === 1){
|
||||
return { object: model, key: arrayPath[0] };
|
||||
}
|
||||
let objectPath = arrayPath.slice(0, -1);
|
||||
let key = arrayPath.slice(-1);
|
||||
let object = get(model, objectPath);
|
||||
let objectPath = arrayPath.slice(0, -1);
|
||||
let object = model;
|
||||
// Ensure that nested objects exist before navigating them
|
||||
objectPath.forEach(pathKey => {
|
||||
let newObject = object[pathKey];
|
||||
if (!newObject){
|
||||
newObject = {};
|
||||
set(object, pathKey, newObject);
|
||||
}
|
||||
object = newObject;
|
||||
});
|
||||
return {object, key};
|
||||
}
|
||||
|
||||
@@ -41,7 +50,8 @@ const schemaFormMixin = {
|
||||
methods: {
|
||||
// Sets the value at the given path
|
||||
change({path, value, ack}){
|
||||
let {object, key} = resolvePath(this.model, path);
|
||||
let {object, key} = resolvePath(this.model, path, this.$set);
|
||||
|
||||
this.$set(object, key, value);
|
||||
if (ack) ack();
|
||||
},
|
||||
@@ -54,7 +64,7 @@ const schemaFormMixin = {
|
||||
if (ack) ack();
|
||||
},
|
||||
pull({path, ack}){
|
||||
let {object, key} = resolvePath(this.model, path);
|
||||
let {object, key} = resolvePath(this.model, path, this.$set);
|
||||
if (!object || !object.splice){
|
||||
throw `${path.join('.')} is ${object}, doesnt have "splice"`
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<script lang="js">
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
//import doAction from '/imports/api/creature/actions/doAction.js';
|
||||
import doAction from '/imports/api/engine/actions/doAction.js';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
},
|
||||
meteor: {
|
||||
baseEffects(){
|
||||
if (this.context.creatureId){
|
||||
if (this.context.creatureId && this.model.variableName){
|
||||
let creatureId = this.context.creatureId;
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
@@ -142,7 +142,7 @@
|
||||
}
|
||||
},
|
||||
effects(){
|
||||
if (this.context.creatureId){
|
||||
if (this.context.creatureId && this.model.variableName){
|
||||
let creatureId = this.context.creatureId;
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"author": "Stefan Zermatten",
|
||||
"scripts": {
|
||||
"run": "meteor --once",
|
||||
"test": "meteor test --driver-package meteortesting:mocha"
|
||||
"test": "meteor test --driver-package meteortesting:mocha --port 3001"
|
||||
},
|
||||
"engines": {
|
||||
"node": "12.16.x",
|
||||
|
||||
Reference in New Issue
Block a user