Compare commits
32 Commits
2.0-beta.3
...
2.0-beta.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6ed9ffb74 | ||
|
|
a84da7d8a5 | ||
|
|
249aebea0f | ||
|
|
11a527481e | ||
|
|
8d729216b5 | ||
|
|
1677e8c424 | ||
|
|
987aacbb67 | ||
|
|
2714d0b9d5 | ||
|
|
1d98c41168 | ||
|
|
e42ec4b862 | ||
|
|
59fc5ab851 | ||
|
|
5d14c392e8 | ||
|
|
c6ca8c1fa4 | ||
|
|
28307e26c3 | ||
|
|
6d42eb62f0 | ||
|
|
877c9ca099 | ||
|
|
9b652fc133 | ||
|
|
7d66c06107 | ||
|
|
21629138f0 | ||
|
|
59a488256b | ||
|
|
766519b4a3 | ||
|
|
e7f73d0e54 | ||
|
|
193d5eec50 | ||
|
|
9284c9ad76 | ||
|
|
f86152675f | ||
|
|
cbac5264cd | ||
|
|
34e3325464 | ||
|
|
79c9e67ce2 | ||
|
|
4c2aabf90d | ||
|
|
48331d3806 | ||
|
|
45f05d0d34 | ||
|
|
58629c92f4 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build
|
||||
@@ -24,7 +24,7 @@ const damageProperty = new ValidatedMethod({
|
||||
run({ _id, operation, value }) {
|
||||
|
||||
// Get action context
|
||||
const prop = CreatureProperties.findOne(_id);
|
||||
let prop = CreatureProperties.findOne(_id);
|
||||
if (!prop) throw new Meteor.Error(
|
||||
'Damage property failed', 'Property doesn\'t exist'
|
||||
);
|
||||
@@ -42,6 +42,14 @@ const damageProperty = new ValidatedMethod({
|
||||
`Property of type "${prop.type}" can't be damaged`
|
||||
);
|
||||
}
|
||||
|
||||
// Replace the prop by its actionContext counterpart if possible
|
||||
if (prop.variableName) {
|
||||
const actionContextProp = actionContext.scope[prop.variableName];
|
||||
if (actionContextProp?._id === prop._id) {
|
||||
prop = actionContextProp;
|
||||
}
|
||||
}
|
||||
|
||||
const result = damagePropertyWork({ prop, operation, value, actionContext });
|
||||
|
||||
@@ -94,6 +102,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
||||
}, {
|
||||
selector: prop
|
||||
});
|
||||
// Also write it straight to the prop so that it is updated in the actionContext
|
||||
prop.damage = damage;
|
||||
prop.value = newValue;
|
||||
} else if (operation === 'increment'){
|
||||
let currentValue = prop.value || 0;
|
||||
let currentDamage = prop.damage || 0;
|
||||
@@ -111,6 +122,9 @@ export function damagePropertyWork({ prop, operation, value, actionContext }) {
|
||||
}, {
|
||||
selector: prop
|
||||
});
|
||||
// Also write it straight to the prop so that it is updated in the actionContext
|
||||
prop.damage += increment;
|
||||
prop.value -= increment;
|
||||
}
|
||||
|
||||
applyTriggers(actionContext.triggers?.damageProperty?.after, prop, actionContext);
|
||||
|
||||
@@ -2,6 +2,7 @@ import action from './applyPropertyByType/applyAction.js';
|
||||
import adjustment from './applyPropertyByType/applyAdjustment.js';
|
||||
import branch from './applyPropertyByType/applyBranch.js';
|
||||
import buff from './applyPropertyByType/applyBuff.js';
|
||||
import buffRemover from './applyPropertyByType/applyBuffRemover.js';
|
||||
import damage from './applyPropertyByType/applyDamage.js';
|
||||
import note from './applyPropertyByType/applyNote.js';
|
||||
import roll from './applyPropertyByType/applyRoll.js';
|
||||
@@ -13,6 +14,7 @@ const applyPropertyByType = {
|
||||
adjustment,
|
||||
branch,
|
||||
buff,
|
||||
buffRemover,
|
||||
damage,
|
||||
note,
|
||||
roll,
|
||||
|
||||
@@ -11,8 +11,8 @@ import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js'
|
||||
export default function applyAction(node, actionContext) {
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
const prop = node.node;
|
||||
let targets = actionContext.targets;
|
||||
if (prop.target === 'self') targets = [actionContext.creature];
|
||||
if (prop.target === 'self') actionContext.targets = [actionContext.creature];
|
||||
const targets = actionContext.targets;
|
||||
|
||||
// Log the name and summary
|
||||
let content = { name: prop.name };
|
||||
@@ -20,7 +20,7 @@ export default function applyAction(node, actionContext) {
|
||||
recalculateInlineCalculations(prop.summary, actionContext);
|
||||
content.value = prop.summary.value;
|
||||
}
|
||||
actionContext.addLog(content);
|
||||
if (!prop.silent) actionContext.addLog(content);
|
||||
|
||||
// Spend the resources
|
||||
const failed = spendResources(prop, actionContext);
|
||||
@@ -188,7 +188,7 @@ function applyChildren(node, actionContext) {
|
||||
function spendResources(prop, actionContext){
|
||||
// Check Uses
|
||||
if (prop.usesLeft <= 0){
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Error',
|
||||
value: `${prop.name || 'action'} does not have enough uses left`,
|
||||
});
|
||||
@@ -196,7 +196,7 @@ function spendResources(prop, actionContext){
|
||||
}
|
||||
// Resources
|
||||
if (prop.insufficientResources){
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Error',
|
||||
value: 'This creature doesn\'t have sufficient resources to perform this action',
|
||||
});
|
||||
@@ -257,7 +257,7 @@ function spendResources(prop, actionContext){
|
||||
}, {
|
||||
selector: prop
|
||||
});
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Uses left',
|
||||
value: prop.usesLeft - 1,
|
||||
inline: true,
|
||||
@@ -288,12 +288,12 @@ function spendResources(prop, actionContext){
|
||||
});
|
||||
|
||||
// Log all the spending
|
||||
if (gainLog.length) actionContext.addLog({
|
||||
if (gainLog.length && !prop.silent) actionContext.addLog({
|
||||
name: 'Gained',
|
||||
value: gainLog.join('\n'),
|
||||
inline: true,
|
||||
});
|
||||
if (spendLog.length) actionContext.addLog({
|
||||
if (spendLog.length && !prop.silent) actionContext.addLog({
|
||||
name: 'Spent',
|
||||
value: spendLog.join('\n'),
|
||||
inline: true,
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function applyAdjustment(node, actionContext){
|
||||
damageTargets.forEach(target => {
|
||||
let stat = target.variables[prop.stat];
|
||||
if (!stat?.type) {
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Error',
|
||||
value: `Could not apply attribute damage, creature does not have \`${prop.stat}\` set`
|
||||
});
|
||||
@@ -36,7 +36,7 @@ export default function applyAdjustment(node, actionContext){
|
||||
value,
|
||||
actionContext,
|
||||
});
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
@@ -44,7 +44,7 @@ export default function applyAdjustment(node, actionContext){
|
||||
});
|
||||
});
|
||||
} else {
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Attribute damage',
|
||||
value: `${prop.stat}${prop.operation === 'set' ? ' set to' : ''}` +
|
||||
` ${value}`,
|
||||
|
||||
@@ -36,25 +36,25 @@ export default function applyBranch(node, actionContext){
|
||||
break;
|
||||
case 'hit':
|
||||
if (scope['$attackHit']?.value){
|
||||
if (!targets.length) actionContext.addLog({value: '**On hit**'});
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On hit**'});
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'miss':
|
||||
if (scope['$attackMiss']?.value){
|
||||
if (!targets.length) actionContext.addLog({value: '**On miss**'});
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On miss**'});
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'failedSave':
|
||||
if (scope['$saveFailed']?.value){
|
||||
if (!targets.length) actionContext.addLog({value: '**On failed save**'});
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On failed save**'});
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
case 'successfulSave':
|
||||
if (scope['$saveSucceeded']?.value){
|
||||
if (!targets.length) actionContext.addLog({value: '**On save**',});
|
||||
if (!targets.length && !prop.silent) actionContext.addLog({value: '**On save**',});
|
||||
applyChildren();
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -13,6 +13,7 @@ import logErrors from './shared/logErrors.js';
|
||||
import { insertCreatureLog } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import cyrb53 from '/imports/api/engine/computation/utility/cyrb53.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js';
|
||||
|
||||
export default function applyBuff(node, actionContext){
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
@@ -21,14 +22,20 @@ export default function applyBuff(node, actionContext){
|
||||
|
||||
// Then copy the decendants of the buff to the targets
|
||||
let propList = [prop];
|
||||
function addChildrenToPropList(children){
|
||||
function addChildrenToPropList(children, { skipCrystalize } = {}){
|
||||
children.forEach(child => {
|
||||
if (skipCrystalize) child.node._skipCrystalize = true;
|
||||
propList.push(child.node);
|
||||
addChildrenToPropList(child.children);
|
||||
// recursively add the child's children, but don't crystalize nested buffs
|
||||
addChildrenToPropList(child.children, {
|
||||
skipCrystalize: skipCrystalize || child.node.type === 'buff'
|
||||
});
|
||||
});
|
||||
}
|
||||
addChildrenToPropList(node.children);
|
||||
crystalizeVariables({propList, actionContext});
|
||||
if (!prop.skipCrystalization) {
|
||||
crystalizeVariables({propList, actionContext});
|
||||
}
|
||||
|
||||
let oldParent = {
|
||||
id: prop.parent.id,
|
||||
@@ -39,7 +46,7 @@ export default function applyBuff(node, actionContext){
|
||||
copyNodeListToTarget(propList, target, oldParent);
|
||||
|
||||
//Log the buff
|
||||
if (prop.name || prop.description?.value){
|
||||
if ((prop.name || prop.description?.value) && !prop.silent){
|
||||
if (target._id === actionContext.creature._id){
|
||||
// Targeting self
|
||||
actionContext.addLog({
|
||||
@@ -88,6 +95,11 @@ function copyNodeListToTarget(propList, target, oldParent){
|
||||
*/
|
||||
function crystalizeVariables({propList, actionContext}){
|
||||
propList.forEach(prop => {
|
||||
if (prop._skipCrystalize) {
|
||||
delete prop._skipCrystalize;
|
||||
return;
|
||||
}
|
||||
// Iterate through all the calculations and crystalize them
|
||||
computedSchemas[prop.type].computedFields().forEach( calcKey => {
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const calcObj = get(prop, key);
|
||||
@@ -124,5 +136,36 @@ function crystalizeVariables({propList, actionContext}){
|
||||
calcObj.hash = cyrb53(calcObj.calculation);
|
||||
});
|
||||
});
|
||||
// For each key in the schema
|
||||
computedSchemas[prop.type].inlineCalculationFields().forEach( calcKey => {
|
||||
// That ends in .inlineCalculations
|
||||
applyFnToKey(prop, calcKey, (prop, key) => {
|
||||
const inlineCalcObj = get(prop, key);
|
||||
if (!inlineCalcObj) return;
|
||||
|
||||
// If there is no text, skip
|
||||
if (!inlineCalcObj.text){
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace all the existing calculations
|
||||
let index = -1;
|
||||
inlineCalcObj.text = inlineCalcObj.text.replace(INLINE_CALCULATION_REGEX, () => {
|
||||
index += 1;
|
||||
return `{${inlineCalcObj.inlineCalculations[index].calculation}}`;
|
||||
});
|
||||
|
||||
// Set the value to the uncomputed string
|
||||
inlineCalcObj.value = inlineCalcObj.text;
|
||||
|
||||
// Write a new hash
|
||||
const inlineCalcHash = cyrb53(inlineCalcObj.text);
|
||||
if (inlineCalcHash === inlineCalcObj.hash) {
|
||||
// Skip if nothing changed
|
||||
return;
|
||||
}
|
||||
inlineCalcObj.hash = inlineCalcHash;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { findLast, difference, intersection, filter } from 'lodash';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
|
||||
import { getProperyAncestors, getPropertiesOfType } from '/imports/api/engine/loadCreatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { softRemove } from '/imports/api/parenting/softRemove.js';
|
||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags.js';
|
||||
|
||||
export default function applyBuffRemover(node, actionContext) {
|
||||
// Apply triggers
|
||||
applyNodeTriggers(node, 'before', actionContext);
|
||||
|
||||
const prop = node.node;
|
||||
|
||||
// Log Name
|
||||
if (prop.name && !prop.silent){
|
||||
actionContext.addLog({ name: prop.name });
|
||||
}
|
||||
|
||||
// Remove buffs
|
||||
if (prop.targetParentBuff) {
|
||||
// Remove nearest ancestor buff
|
||||
const ancestors = getProperyAncestors(actionContext.creature._id, prop._id);
|
||||
const nearestBuff = findLast(ancestors, ancestor => ancestor.type === 'buff');
|
||||
if (!nearestBuff) {
|
||||
actionContext.addLog({
|
||||
name: 'Error',
|
||||
value: 'Buff remover does not have a parent buff to remove',
|
||||
});
|
||||
return;
|
||||
}
|
||||
removeBuff(nearestBuff, actionContext, prop);
|
||||
} else {
|
||||
// Get all the buffs targeted by tags
|
||||
const allBuffs = getPropertiesOfType(actionContext.creature._id, 'buff');
|
||||
const targetedBuffs = filter(allBuffs, buff => {
|
||||
if (buff.inactive) return false;
|
||||
if (buffRemoverMatchTags(prop, buff)) return true;
|
||||
});
|
||||
// Remove the buffs
|
||||
if (prop.removeAll) {
|
||||
// Remove all matching buffs
|
||||
targetedBuffs.forEach(buff => {
|
||||
removeBuff(buff, actionContext, prop);
|
||||
});
|
||||
} else {
|
||||
// Sort in reverse order
|
||||
targetedBuffs.sort((a, b) => b.order - a.order);
|
||||
// Remove the one with the highest order
|
||||
const buff = targetedBuffs[0];
|
||||
if (buff) {
|
||||
removeBuff(buff, actionContext, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply triggers
|
||||
applyNodeTriggers(node, 'after', actionContext);
|
||||
// Apply children
|
||||
node.children.forEach(child => applyProperty(child, actionContext));
|
||||
}
|
||||
|
||||
function removeBuff(buff, actionContext, prop) {
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: 'Removed',
|
||||
value: `${buff.name || 'Buff'}`
|
||||
});
|
||||
softRemove({ _id: buff._id, collection: CreatureProperties });
|
||||
}
|
||||
|
||||
function buffRemoverMatchTags(buffRemover, prop) {
|
||||
let matched = false;
|
||||
const propTags = getEffectivePropTags(prop);
|
||||
// Check the target tags
|
||||
if (
|
||||
!buffRemover.targetTags?.length ||
|
||||
difference(buffRemover.targetTags, propTags).length === 0
|
||||
) {
|
||||
matched = true;
|
||||
}
|
||||
// Check the extra tags
|
||||
buffRemover.extraTags?.forEach(extra => {
|
||||
if (extra.operation === 'OR') {
|
||||
if (matched) return;
|
||||
if (
|
||||
!extra.tags.length ||
|
||||
difference(extra.tags, propTags).length === 0
|
||||
) {
|
||||
matched = true;
|
||||
}
|
||||
} else if (extra.operation === 'NOT') {
|
||||
if (
|
||||
extra.tags.length &&
|
||||
intersection(extra.tags, propTags)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return matched;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ export default function applyDamage(node, actionContext){
|
||||
// There are no targets, just log the result
|
||||
logValue.push(`**${damage}** ${suffix}`);
|
||||
}
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: logName,
|
||||
value: logValue.join('\n'),
|
||||
inline: true,
|
||||
@@ -219,6 +219,16 @@ function dealDamage({target, damageType, amount, actionContext}){
|
||||
if (damageType === 'healing') damageLeft = -totalDamage;
|
||||
healthBars.forEach(healthBar => {
|
||||
if (damageLeft === 0) return;
|
||||
// Replace the healthbar by the one in the action context if we can
|
||||
// The damagePropertyWork function bashes the prop with the damage
|
||||
// So we can use the new value in later action properties
|
||||
if (healthBar.variableName) {
|
||||
const targetHealthBar = target.variables[healthBar.variableName];
|
||||
if (targetHealthBar?._id === healthBar._id) {
|
||||
healthBar = targetHealthBar;
|
||||
}
|
||||
}
|
||||
// Do the damage
|
||||
let damageAdded = damagePropertyWork({
|
||||
prop: healthBar,
|
||||
operation: 'increment',
|
||||
|
||||
@@ -20,7 +20,7 @@ export default function applySavingThrow(node, actionContext){
|
||||
});
|
||||
return node.children.forEach(child => applyProperty(child, actionContext));
|
||||
}
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: prop.name,
|
||||
value: `DC **${dc}**`,
|
||||
inline: true,
|
||||
@@ -94,7 +94,7 @@ export default function applySavingThrow(node, actionContext){
|
||||
} else {
|
||||
scope['$saveFailed'] = {value: true};
|
||||
}
|
||||
actionContext.addLog({
|
||||
if (!prop.silent) actionContext.addLog({
|
||||
name: saveSuccess ? 'Successful save' : 'Failed save',
|
||||
value: resultPrefix + '\n**' + result + '**',
|
||||
inline: true,
|
||||
|
||||
@@ -65,7 +65,7 @@ export function applyTrigger(trigger, prop, actionContext) {
|
||||
recalculateInlineCalculations(trigger.description, actionContext);
|
||||
content.value = trigger.description.value;
|
||||
}
|
||||
actionContext.addLog(content);
|
||||
if(!trigger.silent) actionContext.addLog(content);
|
||||
|
||||
// Get all the trigger's properties and apply them
|
||||
const properties = getPropertyDecendants(actionContext.creature._id, trigger._id);
|
||||
|
||||
@@ -9,7 +9,6 @@ import CreatureProperties from '/imports/api/creature/creatureProperties/Creatur
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import { doActionWork } from '/imports/api/engine/actions/doAction.js';
|
||||
import { CreatureLogSchema } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import ActionContext from '/imports/api/engine/actions/ActionContext.js';
|
||||
|
||||
const doAction = new ValidatedMethod({
|
||||
@@ -65,8 +64,6 @@ const doAction = new ValidatedMethod({
|
||||
let slotLevel = spell.level || 0;
|
||||
let slot;
|
||||
|
||||
actionContext.scope['slotLevel'] = slotLevel;
|
||||
|
||||
if (slotId && !spell.castWithoutSpellSlots){
|
||||
slot = CreatureProperties.findOne(slotId);
|
||||
if (!slot){
|
||||
@@ -109,6 +106,8 @@ const doAction = new ValidatedMethod({
|
||||
});
|
||||
}
|
||||
|
||||
actionContext.scope['slotLevel'] = slotLevel;
|
||||
|
||||
// Do the action
|
||||
doActionWork({
|
||||
properties, ancestors, actionContext, methodScope: scope,
|
||||
|
||||
@@ -10,8 +10,10 @@ export default function computeToggleDependencies(node, dependencyGraph){
|
||||
prop.enabled
|
||||
) return;
|
||||
walkDown(node.children, child => {
|
||||
child.node._computationDetails.toggleAncestors.push(prop);
|
||||
// Only for children that aren't inactive
|
||||
if (child.node.inactive) return;
|
||||
// The child nodes depend on the toggle condition compuation
|
||||
child.node._computationDetails.toggleAncestors.push(prop);
|
||||
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const linkDependenciesByType = {
|
||||
effect: linkEffects,
|
||||
proficiency: linkProficiencies,
|
||||
roll: linkRoll,
|
||||
pointBuy: linkPointBuy,
|
||||
propertySlot: linkSlot,
|
||||
skill: linkSkill,
|
||||
spell: linkAction,
|
||||
@@ -105,7 +106,8 @@ function linkBuff(dependencyGraph, prop){
|
||||
dependOnCalc({dependencyGraph, prop, key: 'duration'});
|
||||
}
|
||||
|
||||
function linkClassLevel(dependencyGraph, prop){
|
||||
function linkClassLevel(dependencyGraph, prop) {
|
||||
if (prop.inactive) return;
|
||||
// The variableName of the prop depends on the prop
|
||||
if (prop.variableName && prop.level){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id, 'classLevel');
|
||||
@@ -121,11 +123,16 @@ function linkDamage(dependencyGraph, prop){
|
||||
dependOnCalc({dependencyGraph, prop, key: 'amount'});
|
||||
}
|
||||
|
||||
function linkEffects(dependencyGraph, prop, computation){
|
||||
function linkEffects(dependencyGraph, prop, computation) {
|
||||
// The effect depends on its amount calculation
|
||||
dependOnCalc({dependencyGraph, prop, key: 'amount'});
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'amount' });
|
||||
// Inactive effects aren't going to impact their targeted stats
|
||||
if (prop.inactive) return;
|
||||
// The stats depend on the effect
|
||||
if (prop.targetByTags){
|
||||
if (prop.inactive) {
|
||||
// Inactive effects apply to no stats
|
||||
return;
|
||||
} else if (prop.targetByTags){
|
||||
getEffectTagTargets(prop, computation).forEach(targetId => {
|
||||
const targetProp = computation.propsById[targetId];
|
||||
if (
|
||||
@@ -221,13 +228,14 @@ function linkRoll(dependencyGraph, prop){
|
||||
}
|
||||
|
||||
function linkVariableName(dependencyGraph, prop){
|
||||
// The variableName of the prop depends on the prop
|
||||
if (prop.variableName){
|
||||
// The variableName of the prop depends on the prop if the prop is active
|
||||
if (prop.variableName && !prop.inactive){
|
||||
dependencyGraph.addLink(prop.variableName, prop._id, 'definition');
|
||||
}
|
||||
}
|
||||
|
||||
function linkDamageMultiplier(dependencyGraph, prop){
|
||||
function linkDamageMultiplier(dependencyGraph, prop) {
|
||||
if (prop.inactive) return;
|
||||
prop.damageTypes.forEach(damageType => {
|
||||
// Remove all non-letter characters from the damage name
|
||||
const damageName = damageType.replace(/[^a-z]/gi, '')
|
||||
@@ -235,8 +243,31 @@ function linkDamageMultiplier(dependencyGraph, prop){
|
||||
});
|
||||
}
|
||||
|
||||
function linkPointBuy(dependencyGraph, prop){
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'min' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'max' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'cost' });
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'total' });
|
||||
prop.values?.forEach(row => {
|
||||
// Wrap the document in a new object so we don't bash it unintentionally
|
||||
const pointBuyRow = {
|
||||
...row,
|
||||
type: 'pointBuyRow',
|
||||
tableName: prop.name,
|
||||
tableId: prop._id,
|
||||
}
|
||||
dependencyGraph.addNode(row._id, pointBuyRow);
|
||||
linkVariableName(dependencyGraph, pointBuyRow);
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.min' });
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.max' });
|
||||
dependOnCalc({ dependencyGraph, pointBuyRow, key: 'row.cost' });
|
||||
});
|
||||
if (prop.inactive) return;
|
||||
}
|
||||
|
||||
function linkProficiencies(dependencyGraph, prop){
|
||||
// The stats depend on the proficiency
|
||||
if (prop.inactive) return;
|
||||
prop.stats.forEach(statName => {
|
||||
if (!statName) return;
|
||||
dependencyGraph.addLink(statName, prop._id, prop.type);
|
||||
@@ -248,6 +279,10 @@ function linkSavingThrow(dependencyGraph, prop){
|
||||
}
|
||||
|
||||
function linkSkill(dependencyGraph, prop){
|
||||
// Depends on base value
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
||||
// Link dependents
|
||||
if (prop.inactive) return;
|
||||
linkVariableName(dependencyGraph, prop);
|
||||
// The prop depends on the variable references as the ability
|
||||
if (prop.ability){
|
||||
@@ -255,9 +290,6 @@ 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'});
|
||||
}
|
||||
|
||||
function linkSlot(dependencyGraph, prop){
|
||||
|
||||
@@ -89,6 +89,10 @@ export function buildComputationFromProps(properties, creature, variables){
|
||||
// Walk the property trees computing things that need to be inherited
|
||||
walkDown(forest, node => {
|
||||
computeInactiveStatus(node);
|
||||
});
|
||||
// Inactive status must be complete for the whole tree before toggle deps
|
||||
// are calculated
|
||||
walkDown(forest, node => {
|
||||
computeToggleDependencies(node, dependencyGraph);
|
||||
computeSlotQuantityFilled(node, dependencyGraph);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import _variable from './computeByType/computeVariable.js';
|
||||
import action from './computeByType/computeAction.js';
|
||||
import attribute from './computeByType/computeAttribute.js';
|
||||
import skill from './computeByType/computeSkill.js';
|
||||
import pointBuy from './computeByType/computePointBuy.js';
|
||||
import propertySlot from './computeByType/computeSlot.js';
|
||||
import container from './computeByType/computeContainer.js';
|
||||
import _calculation from './computeByType/computeCalculation.js';
|
||||
@@ -13,6 +14,7 @@ export default Object.freeze({
|
||||
attribute,
|
||||
container,
|
||||
skill,
|
||||
pointBuy,
|
||||
propertySlot,
|
||||
spell: action,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
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.value = min;
|
||||
}
|
||||
if (max !== null && row.value > max) {
|
||||
row.value = max;
|
||||
}
|
||||
// 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 || [];
|
||||
error.message = 'Cost calculation error.\n' + error.message;
|
||||
row.errors.push(error);
|
||||
}
|
||||
});
|
||||
if (Number.isFinite(costFunction.value)) {
|
||||
row.spent = costFunction.value;
|
||||
prop.spent += costFunction.value;
|
||||
}
|
||||
});
|
||||
prop.pointsLeft = (prop.total?.value || 0) - (prop.spent || 0);
|
||||
if (prop.spent > prop.total?.value) {
|
||||
prop.errors = prop.errors || [];
|
||||
prop.errors.push({
|
||||
type: 'pointBuyError',
|
||||
message: 'Spent more than total points available',
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// by computeVariableAsSkill
|
||||
export default function computeSkill(computation, node){
|
||||
const prop = node.data;
|
||||
prop.proficiency = prop.baseProficiency;
|
||||
prop.proficiency = prop.baseProficiency || 0;
|
||||
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
||||
// Multiply the proficiency bonus by the actual proficiency
|
||||
if(prop.proficiency === 0.49){
|
||||
|
||||
@@ -8,7 +8,13 @@ export default function aggregateDefinition({node, linkedNode, link}){
|
||||
// get current defining prop
|
||||
const definingProp = node.data.definingProp;
|
||||
// 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
|
||||
overrideProp(definingProp, node);
|
||||
// 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
|
||||
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;
|
||||
// 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){
|
||||
node.data.baseValue = propBaseValue;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function aggregateEffect({node, linkedNode, link}){
|
||||
name: linkedNode.data.name,
|
||||
operation: linkedNode.data.operation,
|
||||
amount: linkedNode.data.amount && {value: linkedNode.data.amount.value},
|
||||
type: linkedNode.data.type,
|
||||
// ancestors: linkedNode.data.ancestors,
|
||||
});
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ export default function computeVariableAsSkill(computation, node, prop){
|
||||
const aggregator = node.data.effectAggregator;
|
||||
const aggregatorBase = aggregator?.base || 0;
|
||||
|
||||
// Store effects
|
||||
prop.effects = node.data.effects;
|
||||
|
||||
// If there is no aggregator, determine if the prop can hide, then exit
|
||||
if (!aggregator){
|
||||
prop.hide = statBase === undefined &&
|
||||
@@ -71,8 +74,6 @@ export default function computeVariableAsSkill(computation, node, prop){
|
||||
prop.fail = aggregator.fail;
|
||||
// Rollbonus
|
||||
prop.rollBonuses = aggregator.rollBonus;
|
||||
// Store effects
|
||||
prop.effects = node.data.effects;
|
||||
}
|
||||
|
||||
function aggregateAbilityEffects({computation, skillNode, abilityNode}){
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function getAggregatorResult(node){
|
||||
if (aggregator.set !== undefined) {
|
||||
result = aggregator.set;
|
||||
}
|
||||
if (!node.definingProp?.decimal && Number.isFinite(result)){
|
||||
if (!node.data.definingProp?.decimal && Number.isFinite(result)){
|
||||
result = Math.floor(result);
|
||||
} else if (Number.isFinite(result)){
|
||||
result = stripFloatingPointOddities(result);
|
||||
|
||||
@@ -51,11 +51,22 @@ function compute(computation, node){
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation){
|
||||
graph.forEachLinkedNode(nodeId, linkedNode => {
|
||||
if (linkedNode._visitedChildren && !linkedNode._visited){
|
||||
const pather = path.nba(graph, {
|
||||
oriented: true
|
||||
});
|
||||
const loop = pather.find(nodeId, nodeId);
|
||||
if (linkedNode._visitedChildren && !linkedNode._visited) {
|
||||
// This is a dependency loop, find a path from the node to itself
|
||||
// and store that path as a dependency loop error
|
||||
const pather = path.nba(graph, { oriented: true });
|
||||
let loop = [];
|
||||
// Pather doesn't like going from a node to iteself, so find all the
|
||||
// paths going from the next node back to the original node
|
||||
// and return the shortest one
|
||||
graph.forEachLinkedNode(nodeId, nextNode => {
|
||||
const newLoop = pather.find(nextNode.id, nodeId);
|
||||
if (!newLoop.length) return;
|
||||
if (!loop.length || newLoop.length < loop.length - 1) {
|
||||
loop = [linkedNode, ...newLoop];
|
||||
}
|
||||
}, true);
|
||||
|
||||
if (loop.length) {
|
||||
computation.errors.push({
|
||||
type: 'dependencyLoop',
|
||||
|
||||
@@ -9,6 +9,7 @@ export default function getEffectivePropTags(prop) {
|
||||
}
|
||||
|
||||
// Tags for some string properties
|
||||
if (prop.variableName) tags.push(prop.variableName);
|
||||
if (prop.damageType) tags.push(prop.damageType);
|
||||
if (prop.skillType) tags.push(prop.skillType);
|
||||
if (prop.attributeType) tags.push(prop.attributeType);
|
||||
|
||||
@@ -46,7 +46,6 @@ export function getSingleProperty(creatureId, propertyId) {
|
||||
'removed': {$ne: true},
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
fields: { icon: 0 },
|
||||
});
|
||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||
return prop;
|
||||
@@ -65,7 +64,6 @@ export function getProperties(creatureId) {
|
||||
'removed': {$ne: true},
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
fields: { icon: 0 },
|
||||
}).fetch();
|
||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||
return props;
|
||||
@@ -90,7 +88,6 @@ export function getPropertiesOfType(creatureId, propType) {
|
||||
'type': propType,
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
fields: { icon: 0 },
|
||||
}).fetch();
|
||||
// console.timeEnd(`Cache miss on creature properties: ${creatureId}`);
|
||||
return props;
|
||||
@@ -100,14 +97,13 @@ export function getCreature(creatureId) {
|
||||
if (loadedCreatures.has(creatureId)) {
|
||||
const loadedCreature = loadedCreatures.get(creatureId);
|
||||
const creature = loadedCreature.creature;
|
||||
if (creature) return creature;
|
||||
if (creature) {
|
||||
const cloneCreature = EJSON.clone(creature);
|
||||
return cloneCreature;
|
||||
}
|
||||
}
|
||||
// console.time(`Cache miss on Creature: ${creatureId}`);
|
||||
const creature = Creatures.findOne(creatureId, {
|
||||
denormalizedStats: 1,
|
||||
variables: 1,
|
||||
dirty: 1,
|
||||
});
|
||||
const creature = Creatures.findOne(creatureId);
|
||||
// console.timeEnd(`Cache miss on Creature: ${creatureId}`);
|
||||
return creature;
|
||||
}
|
||||
@@ -116,7 +112,10 @@ export function getVariables(creatureId) {
|
||||
if (loadedCreatures.has(creatureId)) {
|
||||
const loadedCreature = loadedCreatures.get(creatureId);
|
||||
const variables = loadedCreature.variables;
|
||||
if (variables) return variables;
|
||||
if (variables) {
|
||||
const cloneVarables = EJSON.clone(variables);
|
||||
return cloneVarables;
|
||||
}
|
||||
}
|
||||
// console.time(`Cache miss on variables: ${creatureId}`);
|
||||
const variables = CreatureVariables.findOne({_creatureId: creatureId});
|
||||
@@ -149,6 +148,7 @@ export function getProperyAncestors(creatureId, propertyId) {
|
||||
// Fetch from database
|
||||
return CreatureProperties.find({
|
||||
_id: { $in: ancestorIds },
|
||||
removed: {$ne: true},
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
@@ -175,6 +175,8 @@ export function getPropertyDecendants(creatureId, propertyId) {
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': propertyId,
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
}).fetch();
|
||||
}
|
||||
}
|
||||
@@ -199,7 +201,6 @@ class LoadedCreature {
|
||||
removed: { $ne: true },
|
||||
}, {
|
||||
sort: { order: 1 },
|
||||
fields: { icon: 0 },
|
||||
}).observeChanges({
|
||||
added(id, fields) {
|
||||
fields._id = id;
|
||||
|
||||
@@ -114,6 +114,11 @@ let ActionSchema = createPropertySchema({
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyActionSchema = createPropertySchema({
|
||||
|
||||
@@ -31,6 +31,11 @@ const AdjustmentSchema = createPropertySchema({
|
||||
allowedValues: ['set', 'increment'],
|
||||
defaultValue: 'increment',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyAdjustmentSchema = createPropertySchema({
|
||||
|
||||
@@ -176,6 +176,7 @@ let ComputedOnlyAttributeSchema = createPropertySchema({
|
||||
effects: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'effects.$': {
|
||||
type: Object,
|
||||
|
||||
@@ -37,6 +37,11 @@ let BranchSchema = createPropertySchema({
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBranchSchema = createPropertySchema({
|
||||
|
||||
84
app/imports/api/properties/BuffRemovers.js
Normal file
84
app/imports/api/properties/BuffRemovers.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
let BuffRemoverSchema = createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
// This will remove just the nearest ancestor buff
|
||||
targetParentBuff: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// The following only applies when not targeting the parent buff
|
||||
// Which character to remove buffs from
|
||||
target: {
|
||||
type: String,
|
||||
allowedValues: [
|
||||
'self',
|
||||
'target',
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// remove 1 or remove all
|
||||
removeAll: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
defaultValue: true,
|
||||
},
|
||||
// Buffs to remove based on tags:
|
||||
targetTags: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'targetTags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
extraTags: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
maxCount: STORAGE_LIMITS.extraTagsCount,
|
||||
},
|
||||
'extraTags.$': {
|
||||
type: Object,
|
||||
},
|
||||
'extraTags.$._id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
'extraTags.$.operation': {
|
||||
type: String,
|
||||
allowedValues: ['OR', 'NOT'],
|
||||
defaultValue: 'OR',
|
||||
},
|
||||
'extraTags.$.tags': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.tagCount,
|
||||
},
|
||||
'extraTags.$.tags.$': {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBuffRemoverSchema = createPropertySchema({});
|
||||
|
||||
const ComputedBuffRemoverSchema = new SimpleSchema()
|
||||
.extend(BuffRemoverSchema)
|
||||
.extend(ComputedOnlyBuffRemoverSchema);
|
||||
|
||||
export { BuffRemoverSchema, ComputedOnlyBuffRemoverSchema, ComputedBuffRemoverSchema };
|
||||
@@ -12,6 +12,10 @@ let BuffSchema = createPropertySchema({
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
hideRemoveButton: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// How many rounds this buff lasts
|
||||
duration: {
|
||||
type: 'fieldToCompute',
|
||||
@@ -25,6 +29,16 @@ let BuffSchema = createPropertySchema({
|
||||
],
|
||||
defaultValue: 'target',
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
// Prevent the children from being crystalized
|
||||
skipCrystalization: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
let ComputedOnlyBuffSchema = createPropertySchema({
|
||||
|
||||
@@ -26,7 +26,12 @@ const DamageSchema = createPropertySchema({
|
||||
max: STORAGE_LIMITS.calculation,
|
||||
defaultValue: 'slashing',
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
},
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyDamageSchema = createPropertySchema({
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
|
||||
// Folders organize a character sheet into a tree, particularly to group things
|
||||
// like 'race' and 'background'
|
||||
let FolderSchema = new SimpleSchema({
|
||||
let FolderSchema = new createPropertySchema({
|
||||
name: {
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyFolderSchema = new SimpleSchema({});
|
||||
const ComputedOnlyFolderSchema = new createPropertySchema({});
|
||||
|
||||
export { FolderSchema, ComputedOnlyFolderSchema };
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
|
||||
import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js';
|
||||
|
||||
/*
|
||||
* PointBuys are reason-value attached to skills and abilities
|
||||
@@ -13,13 +14,6 @@ let PointBuySchema = createPropertySchema({
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.name,
|
||||
},
|
||||
variableName: {
|
||||
type: String,
|
||||
optional: true,
|
||||
regEx: VARIABLE_NAME_REGEX,
|
||||
min: 2,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
ignored: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
@@ -27,10 +21,18 @@ let PointBuySchema = createPropertySchema({
|
||||
'values': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||
},
|
||||
'values.$': {
|
||||
type: Object,
|
||||
},
|
||||
'values.$._id': {
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
autoValue(){
|
||||
if (!this.isSet) return Random.id();
|
||||
}
|
||||
},
|
||||
'values.$.name': {
|
||||
type: String,
|
||||
optional: true,
|
||||
@@ -47,6 +49,18 @@ let PointBuySchema = createPropertySchema({
|
||||
type: Number,
|
||||
optional: true,
|
||||
},
|
||||
'values.$.min': {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
'values.$.max': {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
'values.$.cost': {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
},
|
||||
min: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
@@ -62,6 +76,7 @@ let PointBuySchema = createPropertySchema({
|
||||
cost: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -74,11 +89,46 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
total: {
|
||||
cost: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
'values': {
|
||||
type: Array,
|
||||
defaultValue: [],
|
||||
maxCount: STORAGE_LIMITS.pointBuyRowsCount,
|
||||
},
|
||||
'values.$': {
|
||||
type: Object,
|
||||
},
|
||||
'values.$.min': {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
cost: {
|
||||
'values.$.max': {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
'values.$.cost': {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
parseLevel: 'compile',
|
||||
},
|
||||
'values.$.spent': {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'values.$.errors': {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'values.$.errors.$': {
|
||||
type: ErrorSchema,
|
||||
},
|
||||
total: {
|
||||
type: 'computedOnlyField',
|
||||
optional: true,
|
||||
},
|
||||
@@ -87,6 +137,19 @@ const ComputedOnlyPointBuySchema = createPropertySchema({
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
pointsLeft: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
errors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'errors.$': {
|
||||
type: ErrorSchema,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedPointBuySchema = new SimpleSchema()
|
||||
|
||||
@@ -30,6 +30,11 @@ let SavingThrowSchema = createPropertySchema({
|
||||
optional: true,
|
||||
max: STORAGE_LIMITS.variableName,
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlySavingThrowSchema = createPropertySchema({
|
||||
|
||||
@@ -135,6 +135,7 @@ let ComputedOnlySkillSchema = createPropertySchema({
|
||||
effects: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
removeBeforeCompute: true,
|
||||
},
|
||||
'effects.$': {
|
||||
type: Object,
|
||||
|
||||
@@ -25,6 +25,7 @@ const actionPropertyTypeOptions = {
|
||||
adjustment: 'Attribute damage',
|
||||
branch: 'Branch',
|
||||
buff: 'Buff',
|
||||
buffRemover: 'Buff Removed',
|
||||
damage: 'Damage',
|
||||
note: 'Note',
|
||||
roll: 'Roll',
|
||||
@@ -108,6 +109,11 @@ let TriggerSchema = createPropertySchema({
|
||||
type: String,
|
||||
max: STORAGE_LIMITS.tagLength,
|
||||
},
|
||||
// Prevent the property from showing up in the log
|
||||
silent: {
|
||||
type: Boolean,
|
||||
optional: true,
|
||||
},
|
||||
});
|
||||
|
||||
const ComputedOnlyTriggerSchema = createPropertySchema({
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ComputedOnlyActionSchema } from '/imports/api/properties/Actions.js';
|
||||
import { ComputedOnlyAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||
import { ComputedOnlyAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||
import { ComputedOnlyBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||
import { ComputedOnlyBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||
import { ComputedOnlyBranchSchema } from '/imports/api/properties/Branches.js';
|
||||
import { ComputedOnlyClassSchema } from '/imports/api/properties/Classes.js';
|
||||
import { ComputedOnlyClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||
@@ -15,6 +16,7 @@ import { ComputedOnlyFeatureSchema } from '/imports/api/properties/Features.js';
|
||||
import { ComputedOnlyFolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { ComputedOnlyItemSchema } from '/imports/api/properties/Items.js';
|
||||
import { ComputedOnlyNoteSchema } from '/imports/api/properties/Notes.js';
|
||||
import { ComputedOnlyPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||
import { ComputedOnlyProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||
import { ComputedOnlyReferenceSchema } from '/imports/api/properties/References.js';
|
||||
import { ComputedOnlyRollSchema } from '/imports/api/properties/Rolls.js';
|
||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
||||
adjustment: ComputedOnlyAdjustmentSchema,
|
||||
attribute: ComputedOnlyAttributeSchema,
|
||||
buff: ComputedOnlyBuffSchema,
|
||||
buffRemover: ComputedOnlyBuffRemoverSchema,
|
||||
branch: ComputedOnlyBranchSchema,
|
||||
class: ComputedOnlyClassSchema,
|
||||
classLevel: ComputedOnlyClassLevelSchema,
|
||||
@@ -44,6 +47,7 @@ const propertySchemasIndex = {
|
||||
folder: ComputedOnlyFolderSchema,
|
||||
item: ComputedOnlyItemSchema,
|
||||
note: ComputedOnlyNoteSchema,
|
||||
pointBuy: ComputedOnlyPointBuySchema,
|
||||
proficiency: ComputedOnlyProficiencySchema,
|
||||
propertySlot: ComputedOnlySlotSchema,
|
||||
reference: ComputedOnlyReferenceSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ComputedActionSchema } from '/imports/api/properties/Actions.js';
|
||||
import { ComputedAdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||
import { ComputedAttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||
import { ComputedBuffSchema } from '/imports/api/properties/Buffs.js';
|
||||
import { ComputedBuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||
import { ComputedBranchSchema } from '/imports/api/properties/Branches.js';
|
||||
import { ComputedClassSchema } from '/imports/api/properties/Classes.js';
|
||||
import { ComputedClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||
@@ -15,6 +16,7 @@ import { ComputedFeatureSchema } from '/imports/api/properties/Features.js';
|
||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { ComputedItemSchema } from '/imports/api/properties/Items.js';
|
||||
import { ComputedNoteSchema } from '/imports/api/properties/Notes.js';
|
||||
import { ComputedPointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||
import { ComputedRollSchema } from '/imports/api/properties/Rolls.js';
|
||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
||||
adjustment: ComputedAdjustmentSchema,
|
||||
attribute: ComputedAttributeSchema,
|
||||
buff: ComputedBuffSchema,
|
||||
buffRemover: ComputedBuffRemoverSchema,
|
||||
branch: ComputedBranchSchema,
|
||||
class: ComputedClassSchema,
|
||||
classLevel: ComputedClassLevelSchema,
|
||||
@@ -42,6 +45,7 @@ const propertySchemasIndex = {
|
||||
feature: ComputedFeatureSchema,
|
||||
folder: FolderSchema,
|
||||
note: ComputedNoteSchema,
|
||||
pointBuy: ComputedPointBuySchema,
|
||||
proficiency: ProficiencySchema,
|
||||
propertySlot: ComputedSlotSchema,
|
||||
reference: ReferenceSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ActionSchema } from '/imports/api/properties/Actions.js';
|
||||
import { AdjustmentSchema } from '/imports/api/properties/Adjustments.js';
|
||||
import { AttributeSchema } from '/imports/api/properties/Attributes.js';
|
||||
import { BuffSchema } from '/imports/api/properties/Buffs.js';
|
||||
import { BuffRemoverSchema } from '/imports/api/properties/BuffRemovers.js';
|
||||
import { BranchSchema } from '/imports/api/properties/Branches.js';
|
||||
import { ClassSchema } from '/imports/api/properties/Classes.js';
|
||||
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
|
||||
@@ -13,6 +14,7 @@ import { EffectSchema } from '/imports/api/properties/Effects.js';
|
||||
import { FeatureSchema } from '/imports/api/properties/Features.js';
|
||||
import { FolderSchema } from '/imports/api/properties/Folders.js';
|
||||
import { NoteSchema } from '/imports/api/properties/Notes.js';
|
||||
import { PointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||
import { ProficiencySchema } from '/imports/api/properties/Proficiencies.js';
|
||||
import { ReferenceSchema } from '/imports/api/properties/References.js';
|
||||
import { RollSchema } from '/imports/api/properties/Rolls.js';
|
||||
@@ -32,6 +34,7 @@ const propertySchemasIndex = {
|
||||
adjustment: AdjustmentSchema,
|
||||
attribute: AttributeSchema,
|
||||
buff: BuffSchema,
|
||||
buffRemover: BuffRemoverSchema,
|
||||
branch: BranchSchema,
|
||||
class: ClassSchema,
|
||||
classLevel: ClassLevelSchema,
|
||||
@@ -42,6 +45,7 @@ const propertySchemasIndex = {
|
||||
feature: FeatureSchema,
|
||||
folder: FolderSchema,
|
||||
note: NoteSchema,
|
||||
pointBuy: PointBuySchema,
|
||||
proficiency: ProficiencySchema,
|
||||
propertySlot: SlotSchema,
|
||||
reference: ReferenceSchema,
|
||||
|
||||
@@ -24,6 +24,12 @@ const PROPERTIES = Object.freeze({
|
||||
helpText: 'When a buff is activated as a child of an action, it will copy the properties under itself onto a target character.',
|
||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||
},
|
||||
buffRemover: {
|
||||
icon: '$vuetify.icons.buffRemover',
|
||||
name: 'Remove Buff',
|
||||
helpText: 'Removes a buff from the target character',
|
||||
suggestedParents: ['action', 'attack', 'savingThrow', 'spell', 'branch'],
|
||||
},
|
||||
branch: {
|
||||
icon: 'mdi-file-tree',
|
||||
name: 'Branch',
|
||||
@@ -96,7 +102,13 @@ const PROPERTIES = Object.freeze({
|
||||
icon: 'mdi-note-outline',
|
||||
name: 'Note',
|
||||
helpText: 'Notes about your character and their adventures',
|
||||
suggestedParents: ['folder'],
|
||||
suggestedParents: ['note', 'folder'],
|
||||
},
|
||||
pointBuy: {
|
||||
icon: 'mdi-table',
|
||||
name: 'Point Buy',
|
||||
helpText: 'A point buy table that allows the user to select an array of values that match a given cost',
|
||||
suggestedParents: [],
|
||||
},
|
||||
proficiency: {
|
||||
icon: 'mdi-brightness-1',
|
||||
|
||||
@@ -32,6 +32,7 @@ const STORAGE_LIMITS = Object.freeze({
|
||||
tagCount: 64,
|
||||
writersCount: 20,
|
||||
libraryCollectionCount: 32,
|
||||
pointBuyRowsCount: 32,
|
||||
});
|
||||
|
||||
export default STORAGE_LIMITS;
|
||||
|
||||
@@ -59,6 +59,10 @@ const SVG_ICONS = Object.freeze({
|
||||
name: 'buff',
|
||||
shape: 'M331.924 20.385c-36.708.887-82.53 60.972-116.063 147.972h.003c30.564-65.57 71.17-106.39 97.348-99.378 28.058 7.516 37.11 69.42 24.847 148.405-.895-.32-1.773-.642-2.672-.96.893.367 1.765.738 2.65 1.106-2.988 19.215-7.22 39.424-12.767 60.12-2.77 10.332-5.763 20.39-8.936 30.14-24.996-3.82-52.374-9.537-80.82-17.16-105.856-28.36-186.115-72.12-179.307-97.53 4.257-15.884 42.167-23.775 95.908-20.29-74.427-8.7-128.912-2.044-135.035 20.803-9.038 33.73 89.168 89.372 219.147 124.2 24.436 6.55 48.267 11.897 70.918 16.042-28.965 75.878-68.293 126.078-96.653 118.48-21.817-5.85-35.995-45.443-36.316-100.206-4.79 75.476 9.278 131.945 40.66 140.356 38.836 10.407 91.394-54.998 127.896-152.98 80.12 10.74 138.958 4.278 145.38-19.682 6.384-23.82-41.025-58.44-115.102-89.03 20.713-109.022 8.483-198.5-31.96-209.34-2.968-.796-6.013-1.144-9.124-1.07zm40.568 213.086c44.65 22.992 71.146 47.135 67.07 62.348-4.055 15.13-38.104 20.457-87.333 16.303 3.415-10.604 6.64-21.502 9.63-32.663 4.176-15.588 7.713-30.965 10.632-45.986z',
|
||||
},
|
||||
'perpendicular-rings-crossed-out': {
|
||||
name: 'buffRemover',
|
||||
shape: 'm 342.85706,29.520771 -10e-4,0.0017 C 311.09371,29.014264 281.87055,64.906082 247.60595,132.05856 L 172.54472,18.060082 142.22614,39.094548 433.13139,479.99983 459.83281,455.56605 385.30374,345.07055 c 60.47709,6.76324 95.40448,9.77027 101.5894,-10.08043 7.33528,-23.5444 -38.64515,-60.03954 -111.4339,-93.57921 25.07382,-108.10207 16.44748,-197.999126 -23.52751,-210.454283 -2.93364,-0.914533 -5.96321,-1.384872 -9.07467,-1.435856 z M 318.01727,76.44599 c 1.43939,0.165657 2.83703,0.458385 4.19071,0.879905 27.7335,8.636553 34.29121,70.855545 18.86666,149.284215 -0.88143,-0.35568 -1.74564,-0.7136 -2.63114,-1.06745 0.87755,0.40257 1.73435,0.80776 2.60387,1.21101 -3.29527,16.73399 -2.43292,17.48734 -7.73765,35.32955 L 249.50725,134.94386 C 277.16641,91.939481 296.57992,73.978789 318.01727,76.44599 Z M 21.309482,189.96942 c -10.385032,33.3398 85.507398,92.87964 213.982728,132.89854 18.67653,5.81939 37.00521,10.88752 54.75098,15.23025 -7.08963,-10.99585 -14.12857,-21.92647 -21.25144,-32.93262 -2.34002,-0.62085 -4.72317,-1.30046 -7.07998,-1.94408 -8.56911,-2.33994 -17.21776,-4.79347 -26.04044,-7.54193 C 131.03978,263.09208 52.602729,216.14328 60.425516,191.02722 65.316857,175.32699 103.51348,168.9655 157.07104,174.60556 121.54273,171.56302 37.239273,147.38028 21.309482,189.96942 Z M 374.83456,244.0657 c 43.69065,24.76623 69.19628,49.95346 64.51274,64.99048 -4.48269,14.38828 -26.92387,13.08335 -72.99123,7.92393 L 362.747,311.5007 c -5.21026,-7.91039 -3.28718,-12.58648 -0.38327,-21.91074 4.79853,-15.40771 8.94901,-30.63076 12.46879,-45.52239 z m -74.68362,109.69514 c -31.13564,67.76298 -69.48034,110.7402 -95.97392,102.48874 -21.56445,-6.72129 -34.14164,-46.85287 -32.26347,-101.58445 -7.81672,75.22256 3.97274,132.21064 34.99162,141.87493 32.83746,10.2293 77.9122,-34.67 115.55909,-108.20499 -7.40453,-11.47654 -14.88401,-23.0444 -22.31332,-34.57423 z'
|
||||
},
|
||||
'roll': {
|
||||
name: 'roll',
|
||||
shape: 'M 339.33314,69.985523 152.23146,95.161367 297.9199,159.07076 Z m 13.13639,6.106743 -41.41324,89.085234 142.77019,70.18694 z M 286.72755,169.97878 132.07338,102.13811 116.91912,287.72473 Z m 23.20215,10.78603 19.43763,205.72115 132.11738,-131.21375 z m -14.47505,0.7893 -172.49845,119.61061 192.24446,89.36907 z M 115.16567,131.2299 49.03503,273.48548 l 52.96523,18.92787 z m 334.97786,155.72184 -114.74252,113.9631 48.61189,28.29247 z m -329.82146,28.96669 45.63657,144.20053 139.66241,-58.06123 z m -17.69094,-7.89455 -46.061724,-16.46047 81.264174,127.69506 z m 220.42858,102.47108 -107.73294,44.78793 150.00722,-20.18447 z',
|
||||
|
||||
@@ -100,21 +100,18 @@ Meteor.publish('classFillers', function(classId){
|
||||
}
|
||||
|
||||
// Get all the ids of libraries the user can access
|
||||
const user = Meteor.users.findOne(userId, {
|
||||
fields: {subscribedLibraries: 1}
|
||||
});
|
||||
const subs = user && user.subscribedLibraries || [];
|
||||
let libraries = Libraries.find({
|
||||
const creatureId = classProp.ancestors[0].id;
|
||||
const libraryIds = getCreatureLibraryIds(creatureId, userId);
|
||||
const libraries = Libraries.find({
|
||||
$or: [
|
||||
{owner: userId},
|
||||
{writers: userId},
|
||||
{readers: userId},
|
||||
{_id: {$in: subs}},
|
||||
{ owner: userId },
|
||||
{ writers: userId },
|
||||
{ readers: userId },
|
||||
{ _id: { $in: libraryIds }, public: true },
|
||||
]
|
||||
}, {
|
||||
fields: {_id: 1, name: 1},
|
||||
sort: { name: 1 }
|
||||
});
|
||||
let libraryIds = libraries.map(lib => lib._id);
|
||||
|
||||
// Build a filter for nodes in those libraries that match the slot
|
||||
let filter = getSlotFillFilter({slot: classProp, libraryIds});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables';
|
||||
import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
import VERSION from '/imports/constants/VERSION.js';
|
||||
@@ -40,6 +41,9 @@ Meteor.publish('api-creature', function(creatureId){
|
||||
CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
}),
|
||||
CreatureVariables.find({
|
||||
_creatureId: creatureId,
|
||||
}),
|
||||
];
|
||||
}, {
|
||||
url: 'api/creature/:0'
|
||||
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
<style lang="css">
|
||||
.column-layout {
|
||||
column-count: 12;
|
||||
column-fill: balance;
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
transform: translateZ(0);
|
||||
padding: 4px;
|
||||
}
|
||||
.column-layout >>> > div {
|
||||
.column-layout > div, .column-layout > span > div {
|
||||
/*
|
||||
Table and width set because firefox does not support break-inside: avoid
|
||||
*/
|
||||
|
||||
72
app/imports/ui/components/global/SmartBtn.vue
Normal file
72
app/imports/ui/components/global/SmartBtn.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template lang="html">
|
||||
<v-btn
|
||||
v-bind="$attrs"
|
||||
:disabled="isDisabled"
|
||||
:loading="loading"
|
||||
@click="click"
|
||||
>
|
||||
<slot />
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import { debounce } from 'lodash';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
disabled: Boolean,
|
||||
debounce: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
timesClicked: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isDisabled(){
|
||||
return this.context.editPermission === false || this.disabled;
|
||||
},
|
||||
debounceTime() {
|
||||
if (Number.isFinite(this.debounce)){
|
||||
return this.debounce;
|
||||
} else if (Number.isFinite(this.context.debounceTime)){
|
||||
return this.context.debounceTime;
|
||||
} else {
|
||||
return 750;
|
||||
}
|
||||
},
|
||||
},
|
||||
created(){
|
||||
this.debounceClicks = debounce(this.clicks, this.debounceTime);
|
||||
},
|
||||
beforeDestroy(){
|
||||
this.debounceClicks.flush();
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
this.timesClicked += 1;
|
||||
this.debounceClicks();
|
||||
this.$emit('click', this.acknowledgeChange);
|
||||
},
|
||||
clicks() {
|
||||
this.$emit('clicks', this.timesClicked, this.acknowledgeChange);
|
||||
this.loading = true;
|
||||
this.timesClicked = 0;
|
||||
},
|
||||
acknowledgeChange(error){
|
||||
this.loading = false;
|
||||
if (error) {
|
||||
snackbar({ text: error.reason || error.message || error.toString() });
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -115,7 +115,7 @@ export default {
|
||||
},
|
||||
change(val){
|
||||
this.dirty = true;
|
||||
if (this.hasChangeListener) this.loading = true;
|
||||
if (this.hasChangeListener()) this.loading = true;
|
||||
this.$emit('change', val, this.acknowledgeChange);
|
||||
},
|
||||
hasChangeListener(){
|
||||
|
||||
34
app/imports/ui/components/global/SmartSlider.vue
Normal file
34
app/imports/ui/components/global/SmartSlider.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template lang="html">
|
||||
<v-slider
|
||||
ref="input"
|
||||
v-bind="$attrs"
|
||||
class="dc-text-field"
|
||||
:hide-details="!(errors && errors.length)"
|
||||
:loading="loading"
|
||||
:error-messages="errors"
|
||||
:value="safeValue"
|
||||
:disabled="isDisabled"
|
||||
:outlined="!regular"
|
||||
@change="change"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
>
|
||||
<template #prepend>
|
||||
<slot name="prepend" />
|
||||
</template>
|
||||
<template #append>
|
||||
<slot name="append" />
|
||||
</template>
|
||||
</v-slider>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import SmartInput from '/imports/ui/components/global/SmartInputMixin.js';
|
||||
|
||||
export default {
|
||||
mixins: [SmartInput],
|
||||
props: {
|
||||
regular: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -5,17 +5,21 @@ import IconPicker from '/imports/ui/components/global/IconPicker.vue';
|
||||
import TextField from '/imports/ui/components/global/TextField.vue';
|
||||
import TextArea from '/imports/ui/components/global/TextArea.vue';
|
||||
import SmartSelect from '/imports/ui/components/global/SmartSelect.vue';
|
||||
import SmartBtn from '/imports/ui/components/global/SmartBtn.vue';
|
||||
import SmartCombobox from '/imports/ui/components/global/SmartCombobox.vue';
|
||||
import SmartCheckbox from '/imports/ui/components/global/SmartCheckbox.vue';
|
||||
import SmartSwitch from '/imports/ui/components/global/SmartSwitch.vue';
|
||||
import SvgIcon from '/imports/ui/components/global/SvgIcon.vue';
|
||||
import SmartSlider from '/imports/ui/components/global/SmartSlider.vue';
|
||||
|
||||
Vue.component('DatePicker', DatePicker);
|
||||
Vue.component('IconPicker', IconPicker);
|
||||
Vue.component('TextField', TextField);
|
||||
Vue.component('TextArea', TextArea);
|
||||
Vue.component('SmartSelect', SmartSelect);
|
||||
Vue.component('SmartBtn', SmartBtn);
|
||||
Vue.component('SmartCombobox', SmartCombobox);
|
||||
Vue.component('SmartCheckbox', SmartCheckbox);
|
||||
Vue.component('SmartSlider', SmartSlider);
|
||||
Vue.component('SmartSwitch', SmartSwitch);
|
||||
Vue.component('SvgIcon', SvgIcon);
|
||||
|
||||
@@ -28,11 +28,11 @@
|
||||
</v-btn>
|
||||
<div
|
||||
class="layout align-center justify-start pr-1"
|
||||
style="flex-grow: 0;"
|
||||
>
|
||||
<!--{{node && node.order}}-->
|
||||
<div
|
||||
v-if="isSlot"
|
||||
class="text-truncate"
|
||||
>
|
||||
<span
|
||||
:class="{
|
||||
@@ -47,15 +47,39 @@
|
||||
:model="node"
|
||||
/>
|
||||
</div>
|
||||
<tree-node-view
|
||||
<template
|
||||
v-else
|
||||
:model="node"
|
||||
/>
|
||||
>
|
||||
<tree-node-view
|
||||
:model="node"
|
||||
/>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="node.parent.id === parentSlotId"
|
||||
icon
|
||||
:disabled="context.editPermission === false"
|
||||
@click.stop="remove(node)"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-if="condenseChild">
|
||||
<span class="mr-4">:</span>
|
||||
<tree-node-view
|
||||
:model="children[0].node"
|
||||
/>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
icon
|
||||
:disabled="context.editPermission === false"
|
||||
@click.stop="remove(children[0].node)"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-delete
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,6 +92,7 @@
|
||||
<build-tree-node-list
|
||||
v-if="showExpanded"
|
||||
:children="computedChildren"
|
||||
:parent-slot-id="computedSlotId"
|
||||
@selected="e => $emit('selected', e)"
|
||||
/>
|
||||
<div v-else>
|
||||
@@ -92,136 +117,175 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
/**
|
||||
* TreeNode's are list item views of character properties. Every property which
|
||||
* can belong to the character is shown in the tree view of the character
|
||||
* the tree view shows off the full character structure, and where each part of
|
||||
* character comes from.
|
||||
**/
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import FillSlotButton from '/imports/ui/creature/buildTree/FillSlotButton.vue';
|
||||
import { some } from 'lodash';
|
||||
/**
|
||||
* TreeNode's are list item views of character properties. Every property which
|
||||
* can belong to the character is shown in the tree view of the character
|
||||
* the tree view shows off the full character structure, and where each part of
|
||||
* character comes from.
|
||||
**/
|
||||
import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue';
|
||||
import FillSlotButton from '/imports/ui/creature/buildTree/FillSlotButton.vue';
|
||||
import { some } from 'lodash';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import softRemoveProperty from '/imports/api/creature/creatureProperties/methods/softRemoveProperty.js';
|
||||
import restoreProperty from '/imports/api/creature/creatureProperties/methods/restoreProperty.js';
|
||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||
|
||||
export default {
|
||||
name: 'BuildTreeNode',
|
||||
components: {
|
||||
TreeNodeView,
|
||||
FillSlotButton,
|
||||
export default {
|
||||
name: 'BuildTreeNode',
|
||||
components: {
|
||||
TreeNodeView,
|
||||
FillSlotButton,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
node: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
expanded: false,
|
||||
/* expand if there's a slot needing attention:
|
||||
this.node._descendantCanFill || (
|
||||
this.node.type === 'propertySlot' &&
|
||||
this. node.quantityExpected?.value === 0 ||
|
||||
(this.node.quantityExpected?.value > 1 && this.node.spaceLeft > 0)
|
||||
)
|
||||
*/
|
||||
}},
|
||||
computed: {
|
||||
condenseChild(){
|
||||
return this.node.type === 'propertySlot' &&
|
||||
this.children.length === 1 &&
|
||||
this.children[0].node.type !== 'propertySlot' &&
|
||||
this.node.quantityExpected &&
|
||||
this.node.quantityExpected.value === 1;
|
||||
},
|
||||
isSlot(){
|
||||
return this.node.type === 'propertySlot';
|
||||
},
|
||||
canFill(){
|
||||
return !!this.node._canFill;
|
||||
},
|
||||
canFillWithOne(){
|
||||
return this.isSlot &&
|
||||
this.node.quantityExpected &&
|
||||
this.node.quantityExpected.value === 1 &&
|
||||
this.node.spaceLeft === 1
|
||||
},
|
||||
canFillWithMany(){
|
||||
return this.isSlot && (
|
||||
!this.node.quantityExpected ||
|
||||
this.node.quantityExpected.value === 0 ||
|
||||
(this.node.quantityExpected.value > 1 && this.node.spaceLeft > 0)
|
||||
);
|
||||
},
|
||||
hasChildren(){
|
||||
return !!this.children && !!this.computedChildren.length || this.lazy && !this.expanded;
|
||||
},
|
||||
showExpanded(){
|
||||
return this.canExpand && this.expanded;
|
||||
},
|
||||
computedChildren(){
|
||||
if (this.condenseChild){
|
||||
return this.children[0].children;
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
parentSlotId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
expanded: false,
|
||||
/* expand if there's a slot needing attention:
|
||||
this.node._descendantCanFill || (
|
||||
this.node.type === 'propertySlot' &&
|
||||
this. node.quantityExpected?.value === 0 ||
|
||||
(this.node.quantityExpected?.value > 1 && this.node.spaceLeft > 0)
|
||||
)
|
||||
*/
|
||||
}},
|
||||
computed: {
|
||||
condenseChild(){
|
||||
return this.node.type === 'propertySlot' &&
|
||||
this.children.length === 1 &&
|
||||
this.children[0].node.type !== 'propertySlot' &&
|
||||
this.node.quantityExpected &&
|
||||
this.node.quantityExpected.value === 1;
|
||||
},
|
||||
isSlot(){
|
||||
return this.node.type === 'propertySlot';
|
||||
},
|
||||
canFill(){
|
||||
return !!this.node._canFill;
|
||||
},
|
||||
canFillWithOne(){
|
||||
return this.isSlot &&
|
||||
this.node.quantityExpected &&
|
||||
this.node.quantityExpected.value === 1 &&
|
||||
this.node.spaceLeft === 1
|
||||
},
|
||||
canFillWithMany(){
|
||||
return this.isSlot && (
|
||||
!this.node.quantityExpected ||
|
||||
this.node.quantityExpected.value === 0 ||
|
||||
(this.node.quantityExpected.value > 1 && this.node.spaceLeft > 0)
|
||||
);
|
||||
},
|
||||
hasChildren(){
|
||||
return !!this.children && !!this.computedChildren.length || this.lazy && !this.expanded;
|
||||
},
|
||||
showExpanded(){
|
||||
return this.canExpand && this.expanded;
|
||||
},
|
||||
computedChildren(){
|
||||
if (this.condenseChild){
|
||||
return this.children[0].children;
|
||||
}
|
||||
return this.children;
|
||||
},
|
||||
computedSlotId() {
|
||||
if (this.condenseChild) {
|
||||
if (this.children[0].node.type === 'propertySlot') {
|
||||
return this.children[0].node._id;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
return this.children;
|
||||
},
|
||||
canExpand(){
|
||||
return !!this.computedChildren.length || this.canFillWithMany;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'node._ancestorOfMatchedDocument'(value){
|
||||
this.expanded = !!value ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
|
||||
},
|
||||
'selectedNode.ancestors'(value){
|
||||
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
|
||||
},
|
||||
} else {
|
||||
if (this.node.type === 'propertySlot') {
|
||||
return this.node._id;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.components.BuildTreeNodeList = require('./BuildTreeNodeList.vue').default
|
||||
},
|
||||
};
|
||||
canExpand(){
|
||||
return !!this.computedChildren.length || this.canFillWithMany;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'node._ancestorOfMatchedDocument'(value){
|
||||
this.expanded = !!value ||
|
||||
some(this.selectedNode?.ancestors, ref => ref.id === this.node._id);
|
||||
},
|
||||
'selectedNode.ancestors'(value){
|
||||
this.expanded = !!some(value, ref => ref.id === this.node._id) || this.expanded;
|
||||
},
|
||||
},
|
||||
beforeCreate() {
|
||||
this.$options.components.BuildTreeNodeList = require('./BuildTreeNodeList.vue').default
|
||||
},
|
||||
methods: {
|
||||
remove(model) {
|
||||
const _id = model._id;
|
||||
softRemoveProperty.call({_id});
|
||||
snackbar({
|
||||
text: `Deleted ${getPropertyTitle(model)}`,
|
||||
callbackName: 'undo',
|
||||
callback(){
|
||||
restoreProperty.call({_id});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg) translateZ(0);
|
||||
}
|
||||
.expand-area {
|
||||
box-shadow: -2px 0px 0px 0px #808080;
|
||||
margin-left: 0;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
.empty .drag-area {
|
||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||
}
|
||||
.empty .expand-button {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg) translateZ(0);
|
||||
}
|
||||
.expand-area {
|
||||
box-shadow: -2px 0px 0px 0px #808080;
|
||||
margin-left: 0;
|
||||
}
|
||||
.handle {
|
||||
cursor: move;
|
||||
}
|
||||
.empty .drag-area {
|
||||
box-shadow: -2px 0px 0px 0px rgb(128, 128, 128, 0.4);
|
||||
}
|
||||
.empty .expand-button {
|
||||
opacity: 0.4;
|
||||
}
|
||||
.found {
|
||||
background: rgba(200, 0, 0, 0.1) !important;
|
||||
}
|
||||
.ghost {
|
||||
.ghost {
|
||||
opacity: 0.5;
|
||||
background: rgba(251, 0, 0, 0.3);
|
||||
}
|
||||
.v-icon.v-icon--disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.v-icon.v-icon--disabled {
|
||||
opacity: 0;
|
||||
}
|
||||
.v-icon {
|
||||
transition: none !important;
|
||||
}
|
||||
.theme--light .tree-node-title:hover {
|
||||
background-color: rgba(0,0,0,.04);
|
||||
}
|
||||
.theme--light .tree-node-title:hover {
|
||||
background-color: rgba(0,0,0,.04);
|
||||
}
|
||||
.theme--dark .tree-node-title:hover {
|
||||
background-color: rgba(255,255,255,.04);
|
||||
}
|
||||
background-color: rgba(255,255,255,.04);
|
||||
}
|
||||
.tree-node-title{
|
||||
transition: background ease 0.3s, color ease 0.15s;
|
||||
}
|
||||
|
||||
@@ -5,26 +5,31 @@
|
||||
:key="child.node._id"
|
||||
:node="child.node"
|
||||
:children="child.children"
|
||||
:parent-slot-id="parentSlotId"
|
||||
@selected="e => $emit('selected', e)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import BuildTreeNode from '/imports/ui/creature/buildTree/BuildTreeNode.vue';
|
||||
import BuildTreeNode from '/imports/ui/creature/buildTree/BuildTreeNode.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BuildTreeNode,
|
||||
},
|
||||
props: {
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
expanded: false,
|
||||
}},
|
||||
};
|
||||
export default {
|
||||
components: {
|
||||
BuildTreeNode,
|
||||
},
|
||||
props: {
|
||||
children: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
parentSlotId: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
expanded: false,
|
||||
}},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
Next
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="biographyAlert"
|
||||
:disabled="!!biographyAlert"
|
||||
:text="step < 2"
|
||||
:color="step < 2? '' : 'accent'"
|
||||
@click="submit"
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
v-model="fab"
|
||||
color="primary"
|
||||
fab
|
||||
small
|
||||
data-id="insert-creature-property-fab"
|
||||
class="insert-creature-property-fab"
|
||||
small
|
||||
>
|
||||
<transition
|
||||
name="fab-rotate"
|
||||
@@ -48,12 +48,13 @@
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js';
|
||||
|
||||
function getParentAndOrderFromSelectedTreeNode(creatureId){
|
||||
function getParentAndOrderFromSelectedTreeNode(creatureId, $store){
|
||||
// find the parent based on the currently selected property
|
||||
let el = document.querySelector('.tree-tab .tree-node-title.primary--text');
|
||||
let selectedComponent = el && el.parentElement.__vue__.$parent;
|
||||
let parentRef, order;
|
||||
if (selectedComponent){
|
||||
const onTreeTab = $store.getters.tabNameById(creatureId) === 'tree';
|
||||
if (onTreeTab && selectedComponent){
|
||||
if (selectedComponent.showExpanded){
|
||||
parentRef = {
|
||||
id: selectedComponent.node._id,
|
||||
@@ -156,7 +157,7 @@
|
||||
let creatureId = this.creatureId;
|
||||
let fab = hideFab();
|
||||
|
||||
let {parentRef, order } = getParentAndOrderFromSelectedTreeNode(creatureId);
|
||||
let {parentRef, order } = getParentAndOrderFromSelectedTreeNode(creatureId, this.$store);
|
||||
let parent;
|
||||
try {
|
||||
parent = fetchDocByRef(parentRef);
|
||||
|
||||
@@ -14,17 +14,19 @@
|
||||
<v-fade-transition
|
||||
mode="out-in"
|
||||
>
|
||||
<v-app-bar-title :key="$store.state.pageTitle">
|
||||
<div>
|
||||
{{ $store.state.pageTitle }}
|
||||
</div>
|
||||
</v-app-bar-title>
|
||||
<v-toolbar-title :key="$store.state.pageTitle">
|
||||
{{ $store.state.pageTitle }}
|
||||
</v-toolbar-title>
|
||||
</v-fade-transition>
|
||||
<v-spacer />
|
||||
<v-fade-transition
|
||||
mode="out-in"
|
||||
>
|
||||
<div :key="$route.meta.title">
|
||||
<v-layout
|
||||
:key="$route.meta.title"
|
||||
class="flex-shrink-0 flex-grow-0"
|
||||
justify-end
|
||||
>
|
||||
<template v-if="creature">
|
||||
<shared-icon :model="creature" />
|
||||
<v-menu
|
||||
@@ -68,7 +70,7 @@
|
||||
</v-menu>
|
||||
<v-app-bar-nav-icon @click="toggleRightDrawer" />
|
||||
</template>
|
||||
</div>
|
||||
</v-layout>
|
||||
</v-fade-transition>
|
||||
<v-fade-transition
|
||||
slot="extension"
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12">
|
||||
<slot-cards-to-fill :creature-id="creatureId" />
|
||||
</v-col>
|
||||
<slot-cards-to-fill :creature-id="creatureId" />
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
@@ -20,7 +18,61 @@
|
||||
lg="6"
|
||||
>
|
||||
<v-card class="pb-4">
|
||||
<v-card-title>Slots</v-card-title>
|
||||
<v-card-title style="height: 68px;">
|
||||
Slots
|
||||
<v-spacer />
|
||||
<v-scale-transition>
|
||||
<v-menu
|
||||
bottom
|
||||
left
|
||||
transition="slide-y-transition"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-badge
|
||||
v-show="hiddenCount"
|
||||
slot="activator"
|
||||
color="primary"
|
||||
overlap
|
||||
:value="hiddenCount"
|
||||
:content="hiddenCount"
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>mdi-file-hidden</v-icon>
|
||||
</v-btn>
|
||||
</v-badge>
|
||||
</template>
|
||||
<v-list>
|
||||
<v-subheader>
|
||||
<v-icon class="mr-2">
|
||||
mdi-file-hidden
|
||||
</v-icon>
|
||||
{{ hiddenCount }} hidden {{ hiddenCount > 1 ? 'properties' : 'property' }}
|
||||
</v-subheader>
|
||||
<v-list-item
|
||||
v-for="pointBuy in hiddenPointBuys"
|
||||
:key="pointBuy._id"
|
||||
@click="unhideProp(pointBuy._id)"
|
||||
>
|
||||
<v-list-item-title>
|
||||
{{ getPropertyTitle(pointBuy) }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-for="slot in hiddenSlots"
|
||||
:key="slot._id"
|
||||
@click="unhideProp(slot._id)"
|
||||
>
|
||||
<v-list-item-title>
|
||||
{{ getPropertyTitle(slot) }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-scale-transition>
|
||||
</v-card-title>
|
||||
<build-tree-node-list
|
||||
:children="slotBuildTree"
|
||||
class="mx-2"
|
||||
@@ -135,6 +187,9 @@ import SlotCardsToFill from '/imports/ui/creature/slots/SlotCardsToFill.vue';
|
||||
import CreatureVariables from '../../../../api/creature/creatures/CreatureVariables';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import CharacterErrors from '/imports/ui/creature/character/errors/CharacterErrors.vue';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
||||
import getPropertyTitle from '/imports/ui/properties/shared/getPropertyTitle.js';
|
||||
|
||||
function traverse(tree, callback, parents = []){
|
||||
tree.forEach(node => {
|
||||
@@ -179,7 +234,10 @@ export default {
|
||||
...this.highestLevels,
|
||||
...this.classProperties
|
||||
].sort((a, b) => a.order - b.order);
|
||||
}
|
||||
},
|
||||
hiddenCount() {
|
||||
return this.hiddenSlots.length + this.hiddenPointBuys.length;
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
creature(){
|
||||
@@ -188,6 +246,39 @@ export default {
|
||||
variables() {
|
||||
return CreatureVariables.findOne({ _creatureId: this.creatureId }) || {};
|
||||
},
|
||||
hiddenPointBuys() {
|
||||
return CreatureProperties.find({
|
||||
type: 'pointBuy',
|
||||
'ancestors.id': this.creatureId,
|
||||
ignored: true,
|
||||
pointsLeft: {$ne: 0},
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}).fetch();
|
||||
},
|
||||
hiddenSlots(){
|
||||
return CreatureProperties.find({
|
||||
type: 'propertySlot',
|
||||
'ancestors.id': this.creatureId,
|
||||
ignored: true,
|
||||
$and: [
|
||||
{
|
||||
$or: [
|
||||
{'slotCondition.value': {$nin: [false, 0, '']}},
|
||||
{'slotCondition.value': {$exists: false}},
|
||||
]
|
||||
},{
|
||||
$or: [
|
||||
{ 'quantityExpected.value': {$in: [false, 0, '', undefined]} },
|
||||
{ 'quantityExpected.value': {exists: false} },
|
||||
{spaceLeft: {$gt: 0}},
|
||||
]
|
||||
},
|
||||
],
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}).fetch();
|
||||
},
|
||||
classProperties(){
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
@@ -213,7 +304,7 @@ export default {
|
||||
slotBuildTree(){
|
||||
const slots = CreatureProperties.find({
|
||||
'ancestors.id': this.creatureId,
|
||||
type: 'propertySlot',
|
||||
type: {$in: ['propertySlot', 'pointBuy']},
|
||||
$or: [
|
||||
{'slotCondition.value': {$nin: [false, 0, '']}},
|
||||
{'slotCondition.value': {$exists: false}},
|
||||
@@ -237,16 +328,15 @@ export default {
|
||||
]);
|
||||
traverse(tree, (child, parents) => {
|
||||
const model = child.node;
|
||||
const isSlotWithSpace = model.type === 'propertySlot' &&
|
||||
const isSlotWithSpace = model.type === 'propertySlot' && (
|
||||
model.spaceLeft > 0 ||
|
||||
!model.quantityExpected ||
|
||||
model.quantityExpected.value === 0;
|
||||
model.quantityExpected.value === 0
|
||||
);
|
||||
if(isSlotWithSpace) {
|
||||
model._canFill = true;
|
||||
parents.forEach(node => {
|
||||
if (node.node.type === 'propertySlot'){
|
||||
node.node._descendantCanFill = true;
|
||||
}
|
||||
node.node._descendantCanFill = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -313,6 +403,19 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
getPropertyTitle,
|
||||
unhideProp(_id) {
|
||||
updateCreatureProperty.call({
|
||||
_id,
|
||||
path: ['ignored'],
|
||||
value: false,
|
||||
}, error => {
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason || error.message || error.toString()});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
{{ buff.name }}
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-list-item-action v-if="!buff.hideRemoveButton">
|
||||
<v-btn
|
||||
icon
|
||||
@click.stop="softRemove(buff._id)"
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
:style="`border: solid 1px ${accentColor};`"
|
||||
hover
|
||||
class="slot-card d-flex flex-column"
|
||||
style="max-width: 400px;"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
@click="$emit('click')"
|
||||
@@ -17,7 +16,7 @@
|
||||
<v-card-title>
|
||||
{{ model.name }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-card-text v-if="model.description">
|
||||
<property-description
|
||||
text
|
||||
:model="model.description"
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
<template>
|
||||
<div class="slots-to-fill">
|
||||
<v-slide-y-transition
|
||||
<column-layout wide-columns class="slots-to-fill">
|
||||
<v-fade-transition
|
||||
group
|
||||
leave-absolute
|
||||
hide-on-leave
|
||||
>
|
||||
<slot-card
|
||||
<div
|
||||
v-for="pointBuy in pointBuys"
|
||||
:key="pointBuy._id"
|
||||
style="transition: all 0.3s !important"
|
||||
>
|
||||
<point-buy-card
|
||||
:model="pointBuy"
|
||||
hover
|
||||
@ignore="ignoreProp(pointBuy._id)"
|
||||
@click="editPointBuy(pointBuy._id)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="slot in slots"
|
||||
:key="slot._id"
|
||||
:model="slot"
|
||||
class="ma-1"
|
||||
hover
|
||||
style="display: inline-block !important; transition: all 0.3s !important;"
|
||||
@ignore="ignoreSlot(slot._id)"
|
||||
@click="fillSlot(slot._id)"
|
||||
/>
|
||||
</v-slide-y-transition>
|
||||
</div>
|
||||
style="transition: all 0.3s !important"
|
||||
>
|
||||
<slot-card
|
||||
:model="slot"
|
||||
hover
|
||||
@ignore="ignoreProp(slot._id)"
|
||||
@click="fillSlot(slot._id)"
|
||||
/>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
</column-layout>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import SlotCard from '/imports/ui/creature/slots/SlotCard.vue';
|
||||
import PointBuyCard from '/imports/ui/properties/components/pointBuy/PointBuyCard.vue';
|
||||
import ColumnLayout from '/imports/ui/components/ColumnLayout.vue';
|
||||
import updateCreatureProperty from '/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js';
|
||||
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
@@ -28,12 +45,14 @@ import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
export default {
|
||||
components: {
|
||||
SlotCard,
|
||||
PointBuyCard,
|
||||
ColumnLayout,
|
||||
},
|
||||
inject: {
|
||||
context: { default: {} }
|
||||
},
|
||||
methods: {
|
||||
ignoreSlot(_id){
|
||||
ignoreProp(_id){
|
||||
updateCreatureProperty.call({
|
||||
_id,
|
||||
path: ['ignored'],
|
||||
@@ -70,6 +89,16 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
editPointBuy(_id){
|
||||
this.$store.commit('pushDialogStack', {
|
||||
component: 'creature-property-dialog',
|
||||
elementId: `point-buy-card-${_id}`,
|
||||
data: {
|
||||
_id,
|
||||
startInEditTab: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
slots(){
|
||||
@@ -94,7 +123,16 @@ export default {
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
});
|
||||
}
|
||||
},
|
||||
pointBuys(){
|
||||
return CreatureProperties.find({
|
||||
type: 'pointBuy',
|
||||
'ancestors.id': this.context.creatureId,
|
||||
ignored: { $ne: true },
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
import SingleCardLayout from '/imports/ui/layouts/SingleCardLayout.vue'
|
||||
import Tabletops from '/imports/api/tabletop/Tabletops.js';
|
||||
import insertTabletop from '/imports/api/tabletop/methods/insertTabletop.js';
|
||||
import snackbar from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
<div class="text-body-1 mb-1">
|
||||
{{ displayedText }}
|
||||
</div>
|
||||
<div v-if="!hideBreadcrumbs && model.ancestors">
|
||||
<div v-if="!hideBreadcrumbs && ancestors">
|
||||
<breadcrumbs
|
||||
:model="model"
|
||||
:model="{...model, ancestors}"
|
||||
class="text-caption"
|
||||
no-links
|
||||
no-icons
|
||||
@@ -41,94 +41,101 @@
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
|
||||
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
|
||||
import { isFinite } from 'lodash';
|
||||
import getEffectIcon from '/imports/ui/utility/getEffectIcon.js';
|
||||
import Breadcrumbs from '/imports/ui/creature/creatureProperties/Breadcrumbs.vue';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { isFinite } from 'lodash';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumbs,
|
||||
export default {
|
||||
components: {
|
||||
Breadcrumbs,
|
||||
},
|
||||
props: {
|
||||
hideBreadcrumbs: Boolean,
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
hideBreadcrumbs: Boolean,
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && this.$listeners.click
|
||||
},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && this.$listeners.click
|
||||
},
|
||||
displayedText(){
|
||||
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;
|
||||
}
|
||||
displayedText(){
|
||||
if (this.model.operation === 'conditional'){
|
||||
return this.model.text || this.model.name || this.operation
|
||||
} else {
|
||||
return this.model.name || this.operation
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
meteor: {
|
||||
ancestors() {
|
||||
const prop = CreatureProperties.findOne(this.model._id);
|
||||
return prop && prop.ancestors || [];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<v-card
|
||||
v-if="model"
|
||||
v-bind="$attrs"
|
||||
:data-id="`point-buy-card-${model._id}`"
|
||||
:style="`border: solid 1px ${accentColor};`"
|
||||
hover
|
||||
class="slot-card d-flex flex-column"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<card-highlight
|
||||
:active="hover"
|
||||
/>
|
||||
<v-card-title>
|
||||
{{ model.name || 'Point Buy' }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
{{ model.spent }}
|
||||
<template v-if="model.total && (typeof model.total.value === 'number')">
|
||||
/ {{ model.total && model.total.value }}
|
||||
</template>
|
||||
</v-card-text>
|
||||
<v-spacer />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
icon
|
||||
color="accent"
|
||||
@click.stop="$emit('ignore')"
|
||||
>
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CardHighlight,
|
||||
},
|
||||
inject: {
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){ return {
|
||||
hover: false,
|
||||
}},
|
||||
computed: {
|
||||
accentColor(){
|
||||
if (this.theme.isDark){
|
||||
return this.$vuetify.theme.themes.dark.primary;
|
||||
} else {
|
||||
return this.$vuetify.theme.themes.light.primary;
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@
|
||||
<v-list-item-title class="d-flex align-center">
|
||||
<roll-popup
|
||||
v-if="!hideModifier"
|
||||
class="prof-mod mr-1"
|
||||
class="prof-mod mr-1 flex-shrink-0"
|
||||
button-class="pl-3 pr-2"
|
||||
text
|
||||
:roll-text="displayedModifier"
|
||||
@@ -43,7 +43,7 @@
|
||||
:value="model.proficiency"
|
||||
class="prof-icon ml-3 mr-2"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-truncate">
|
||||
{{ model.name }}
|
||||
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
|
||||
*
|
||||
|
||||
@@ -141,6 +141,18 @@
|
||||
@change="change('usesUsed', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<smart-select
|
||||
label="Reset"
|
||||
|
||||
@@ -59,6 +59,12 @@
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
|
||||
@@ -38,6 +38,12 @@
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
<form-section
|
||||
name="Children"
|
||||
standalone
|
||||
|
||||
@@ -36,22 +36,66 @@
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
</v-expand-transition>
|
||||
<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)"
|
||||
/>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
standalone
|
||||
>
|
||||
<slot name="children" />
|
||||
</form-section>
|
||||
<form-sections>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
standalone
|
||||
>
|
||||
<slot name="children" />
|
||||
</form-section>
|
||||
<form-section
|
||||
name="Advanced"
|
||||
>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Hide remove button"
|
||||
:value="model.hideRemoveButton"
|
||||
:error-messages="errors.hideRemoveButton"
|
||||
@change="change('hideRemoveButton', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Don't freeze variables"
|
||||
:value="model.skipCrystalization"
|
||||
:error-messages="errors.skipCrystalization"
|
||||
@change="change('skipCrystalization', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<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)"
|
||||
/>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -60,12 +104,6 @@
|
||||
|
||||
export default {
|
||||
mixins: [propertyFormMixin],
|
||||
props: {
|
||||
parentTarget: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
targetOptions: [
|
||||
{
|
||||
|
||||
186
app/imports/ui/properties/forms/BuffRemoverForm.vue
Normal file
186
app/imports/ui/properties/forms/BuffRemoverForm.vue
Normal file
@@ -0,0 +1,186 @@
|
||||
<template lang="html">
|
||||
<div class="buff-remover-form">
|
||||
<text-field
|
||||
ref="focusFirst"
|
||||
label="Name"
|
||||
:value="model.name"
|
||||
:error-messages="errors.name"
|
||||
@change="change('name', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Remove parent buff"
|
||||
:value="model.targetParentBuff"
|
||||
:error-messages="errors.targetParentBuff"
|
||||
@change="change('targetParentBuff', ...arguments)"
|
||||
/>
|
||||
<v-expand-transition>
|
||||
<div v-if="!model.targetParentBuff">
|
||||
<smart-switch
|
||||
:label="model.removeAll ? 'Remove All. All matching buffs will be removed' : 'Remove All. Only 1 matching buff will be removed'"
|
||||
:value="model.removeAll"
|
||||
:error-messages="errors.removeAll"
|
||||
@change="change('removeAll', ...arguments)"
|
||||
/>
|
||||
<smart-select
|
||||
label="Target"
|
||||
:items="targetOptions"
|
||||
:value="model.target"
|
||||
:error-messages="errors.target"
|
||||
:menu-props="{auto: true, lazy: true}"
|
||||
@change="change('target', ...arguments)"
|
||||
/>
|
||||
<v-layout
|
||||
align-center
|
||||
>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
class="mr-2"
|
||||
:loading="addExtraTagsLoading"
|
||||
:disabled="extraTagsFull"
|
||||
@click="addExtraTags"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
<smart-combobox
|
||||
label="Tags Required"
|
||||
hint="The effect will apply to properties that have all the listed tags"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
persistent-hint
|
||||
:value="model.targetTags"
|
||||
:error-messages="errors.targetTags"
|
||||
@change="change('targetTags', ...arguments)"
|
||||
/>
|
||||
</v-layout>
|
||||
<v-slide-x-transition
|
||||
v-if="!model.targetParentBuff"
|
||||
group
|
||||
>
|
||||
<div
|
||||
v-for="(extras, i) in model.extraTags"
|
||||
:key="extras._id"
|
||||
class="target-tags layout align-center justify-space-between"
|
||||
>
|
||||
<smart-select
|
||||
label="Operation"
|
||||
style="width: 90px; flex-grow: 0;"
|
||||
:items="['OR', 'NOT']"
|
||||
:value="extras.operation"
|
||||
:error-messages="errors.extraTags && errors.extraTags[i]"
|
||||
@change="change(['extraTags', i, 'operation'], ...arguments)"
|
||||
/>
|
||||
<smart-combobox
|
||||
label="Tags"
|
||||
:hint="extras.operation === 'OR' ? 'The effect will also target properties that have all of these tags instead' : 'The effect will ignore properties that have any of these tags'"
|
||||
class="mx-2"
|
||||
multiple
|
||||
chips
|
||||
deletable-chips
|
||||
persistent-hint
|
||||
:value="extras.tags"
|
||||
@change="change(['extraTags', i, 'tags'], ...arguments)"
|
||||
/>
|
||||
<v-btn
|
||||
icon
|
||||
style="margin-top: -30px;"
|
||||
@click="$emit('pull', {path: ['extraTags', i]})"
|
||||
>
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
<form-sections>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
standalone
|
||||
>
|
||||
<slot name="children" />
|
||||
</form-section>
|
||||
<form-section
|
||||
name="Advanced"
|
||||
>
|
||||
<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"
|
||||
sm="6"
|
||||
md="4"
|
||||
>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import {
|
||||
BuffRemoverSchema
|
||||
} from '/imports/api/properties/BuffRemovers.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyFormMixin],
|
||||
data(){return {
|
||||
addExtraTagsLoading: false,
|
||||
extraTagOperations: ['OR', 'NOT'],
|
||||
targetOptions: [
|
||||
{
|
||||
text: 'Self',
|
||||
value: 'self',
|
||||
}, {
|
||||
text: 'Target',
|
||||
value: 'target',
|
||||
},
|
||||
],
|
||||
}},
|
||||
computed: {
|
||||
extraTagsFull(){
|
||||
if (!this.model.extraTags) return false;
|
||||
let maxCount = BuffRemoverSchema.get('extraTags', 'maxCount');
|
||||
return this.model.extraTags.length >= maxCount;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
acknowledgeAddResult(){
|
||||
this.addExtraTagsLoading = false;
|
||||
},
|
||||
addExtraTags(){
|
||||
this.addExtraTagsLoading = true;
|
||||
this.$emit('push', {
|
||||
path: ['extraTags'],
|
||||
value: {
|
||||
_id: Random.id(),
|
||||
operation: 'OR',
|
||||
tags: [],
|
||||
},
|
||||
ack: this.acknowledgeAddResult,
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
@@ -53,6 +53,12 @@
|
||||
:error-messages="errors.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
|
||||
219
app/imports/ui/properties/forms/PointBuyForm.vue
Normal file
219
app/imports/ui/properties/forms/PointBuyForm.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template lang="html">
|
||||
<div class="point-buy-form">
|
||||
<point-buy-spend-form
|
||||
:model="model"
|
||||
@change="e => $emit('change', e)"
|
||||
@push="e => $emit('change', e)"
|
||||
@pull="e => $emit('change', e)"
|
||||
/>
|
||||
<form-sections>
|
||||
<form-section name="Settings">
|
||||
<v-row dense>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<text-field
|
||||
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>
|
||||
</form-section>
|
||||
<form-section name="Rows">
|
||||
<v-slide-x-transition
|
||||
group
|
||||
leave-absolute
|
||||
>
|
||||
<v-row
|
||||
v-for="(row, i) in model.values"
|
||||
:key="row._id"
|
||||
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
|
||||
v-if="row.errors && row.errors.length"
|
||||
cols="12"
|
||||
>
|
||||
<calculation-error-list
|
||||
:errors="row.errors"
|
||||
/>
|
||||
</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"
|
||||
name="Children"
|
||||
>
|
||||
<slot name="children" />
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import attributeListMixin from '/imports/ui/properties/forms/shared/lists/attributeListMixin.js';
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import { PointBuySchema } from '/imports/api/properties/PointBuys.js';
|
||||
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
|
||||
import PointBuySpendForm from '/imports/ui/properties/forms/PointBuySpendForm.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalculationErrorList,
|
||||
PointBuySpendForm,
|
||||
},
|
||||
mixins: [propertyFormMixin, attributeListMixin],
|
||||
data() {
|
||||
return {
|
||||
addRowLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
rowsFull(){
|
||||
if (!this.model.values) return false;
|
||||
let maxCount = PointBuySchema.get('values', 'maxCount');
|
||||
return this.model.values.length >= maxCount;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
acknowledgeAddResult(){
|
||||
this.addRowLoading = false;
|
||||
},
|
||||
addRow(){
|
||||
this.addRowLoading = true;
|
||||
this.$emit('push', {
|
||||
path: ['values'],
|
||||
value: {
|
||||
_id: Random.id(),
|
||||
},
|
||||
ack: this.acknowledgeAddResult,
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
108
app/imports/ui/properties/forms/PointBuySpendForm.vue
Normal file
108
app/imports/ui/properties/forms/PointBuySpendForm.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template lang="html">
|
||||
<div class="point-buy-spend-form">
|
||||
<v-row
|
||||
v-if="model.values && model.values.length"
|
||||
dense
|
||||
>
|
||||
<v-col
|
||||
cols="10"
|
||||
md="11"
|
||||
/>
|
||||
<v-col
|
||||
cols="2"
|
||||
md="1"
|
||||
class="text-truncate d-flex justify-center"
|
||||
>
|
||||
Cost
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-for="(row, i) in model.values"
|
||||
:key="row._id"
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="2"
|
||||
class="d-flex justify-md-end"
|
||||
>
|
||||
{{ row.name }}
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="2"
|
||||
md="1"
|
||||
class="d-flex justify-md-center justify-end"
|
||||
>
|
||||
{{ row.value }}
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="8"
|
||||
>
|
||||
<smart-slider
|
||||
thumb-label
|
||||
dense
|
||||
:ticks="max(row) - min(row) <= 20"
|
||||
:min="min(row)"
|
||||
:max="max(row)"
|
||||
: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})"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="2"
|
||||
md="1"
|
||||
class="text-truncate d-flex justify-center"
|
||||
>
|
||||
{{ row.spent }}
|
||||
</v-col>
|
||||
<v-col
|
||||
v-if="row.errors && row.errors.length"
|
||||
cols="12"
|
||||
>
|
||||
<calculation-error-list
|
||||
:errors="row.errors"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
dense
|
||||
>
|
||||
<v-col
|
||||
v-if="typeof model.spent === 'number'"
|
||||
cols="12"
|
||||
class="text-h4 mb-4 pr-8 d-flex justify-end"
|
||||
:class="{
|
||||
'error--text': model.spent > (model.total && model.total.value),
|
||||
'warning--text': model.spent < (model.total && model.total.value),
|
||||
}"
|
||||
>
|
||||
{{ model.spent }}
|
||||
<template v-if="model.total && (typeof model.total.value === 'number')">
|
||||
/ {{ model.total && model.total.value }}
|
||||
</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
|
||||
import CalculationErrorList from '/imports/ui/properties/forms/shared/CalculationErrorList.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CalculationErrorList,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
methods: {
|
||||
max(row) {
|
||||
return row.max ? row.max && row.max.value : this.model.max && this.model.max.value;
|
||||
},
|
||||
min(row) {
|
||||
return row.min ? row.min && row.min.value : this.model.min && this.model.min.value;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -65,6 +65,12 @@
|
||||
:error-messages="errors.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
<form-section
|
||||
v-if="$slots.children"
|
||||
name="Children"
|
||||
|
||||
@@ -140,6 +140,12 @@
|
||||
:value="model.tags"
|
||||
@change="change('tags', ...arguments)"
|
||||
/>
|
||||
<smart-switch
|
||||
label="Don't show in log"
|
||||
:value="model.silent"
|
||||
:error-messages="errors.silent"
|
||||
@change="change('silent', ...arguments)"
|
||||
/>
|
||||
</form-section>
|
||||
</form-sections>
|
||||
</div>
|
||||
|
||||
@@ -73,5 +73,8 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
<style lang="css">
|
||||
.error-list .v-alert__content{
|
||||
overflow-x: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@change="(value, ack) => $emit('change', {path: ['calculation'], value, ack})"
|
||||
>
|
||||
<template
|
||||
v-if="model.value !== undefined || model.value !== null"
|
||||
v-if="showValue"
|
||||
#value
|
||||
>
|
||||
{{ model.value }}
|
||||
@@ -28,8 +28,20 @@ export default {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
hideValue: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showValue() {
|
||||
const value = this.model.value;
|
||||
if (
|
||||
this.hideValue ||
|
||||
(value === undefined || value === null) ||
|
||||
value == this.model.calculation
|
||||
) return false;
|
||||
return true;
|
||||
},
|
||||
errorList(){
|
||||
if (this.model.parseError){
|
||||
return [this.model.parseError, ...this.model.errors];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<v-expansion-panel-header>
|
||||
{{ name }}
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-expansion-panel-content class="pt-2">
|
||||
<slot />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
@@ -13,7 +13,7 @@
|
||||
<v-expansion-panel-header>
|
||||
{{ name }}
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-expansion-panel-content class="pt-2">
|
||||
<slot />
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
|
||||
@@ -2,6 +2,7 @@ const ActionForm = () => import('/imports/ui/properties/forms/ActionForm.vue');
|
||||
const AdjustmentForm = () => import('/imports/ui/properties/forms/AdjustmentForm.vue');
|
||||
const AttributeForm = () => import('/imports/ui/properties/forms/AttributeForm.vue');
|
||||
const BuffForm = () => import('/imports/ui/properties/forms/BuffForm.vue');
|
||||
const BuffRemoverForm = () => import('/imports/ui/properties/forms/BuffRemoverForm.vue');
|
||||
const BranchForm = () => import('/imports/ui/properties/forms/BranchForm.vue');
|
||||
const ClassForm = () => import('/imports/ui/properties/forms/ClassForm.vue');
|
||||
const ClassLevelForm = () => import('/imports/ui/properties/forms/ClassLevelForm.vue');
|
||||
@@ -14,6 +15,7 @@ const FeatureForm = () => import('/imports/ui/properties/forms/FeatureForm.vue')
|
||||
const FolderForm = () => import('/imports/ui/properties/forms/FolderForm.vue');
|
||||
const ItemForm = () => import('/imports/ui/properties/forms/ItemForm.vue');
|
||||
const NoteForm = () => import('/imports/ui/properties/forms/NoteForm.vue');
|
||||
const PointBuyForm = () => import('/imports/ui/properties/forms/PointBuyForm.vue');
|
||||
const ProficiencyForm = () => import('/imports/ui/properties/forms/ProficiencyForm.vue');
|
||||
const ReferenceForm = () => import('/imports/ui/properties/forms/ReferenceForm.vue');
|
||||
const RollForm = () => import('/imports/ui/properties/forms/RollForm.vue');
|
||||
@@ -31,6 +33,7 @@ export default {
|
||||
adjustment: AdjustmentForm,
|
||||
attribute: AttributeForm,
|
||||
buff: BuffForm,
|
||||
buffRemover: BuffRemoverForm,
|
||||
branch: BranchForm,
|
||||
constant: ConstantForm,
|
||||
container: ContainerForm,
|
||||
@@ -43,6 +46,7 @@ export default {
|
||||
folder: FolderForm,
|
||||
item: ItemForm,
|
||||
note: NoteForm,
|
||||
pointBuy: PointBuyForm,
|
||||
proficiency: ProficiencyForm,
|
||||
propertySlot: SlotForm,
|
||||
reference: ReferenceForm,
|
||||
|
||||
@@ -107,25 +107,18 @@
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<property-field
|
||||
v-if="baseEffects.length || effects.length"
|
||||
v-if="effects && effects.length"
|
||||
:cols="{col: 12}"
|
||||
name="Effects"
|
||||
>
|
||||
<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
|
||||
v-for="effect in effects"
|
||||
:key="effect._id"
|
||||
:model="effect"
|
||||
:data-id="effect._id"
|
||||
@click="clickEffect(effect._id)"
|
||||
:hide-breadcrumbs="effect._id === model._id"
|
||||
@click="effect._id !== model._id && clickEffect(effect._id)"
|
||||
/>
|
||||
</v-list>
|
||||
</property-field>
|
||||
@@ -137,7 +130,6 @@
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
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 IncrementButton from '/imports/ui/components/IncrementButton.vue';
|
||||
import getProficiencyIcon from '/imports/ui/utility/getProficiencyIcon.js';
|
||||
@@ -211,31 +203,8 @@
|
||||
},
|
||||
},
|
||||
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() {
|
||||
return CreatureProperties.find({
|
||||
_id: { $in: this.model.effects?.map(e => e._id) || [] }
|
||||
});
|
||||
return this.model.effects;
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
97
app/imports/ui/properties/viewers/BuffRemoverViewer.vue
Normal file
97
app/imports/ui/properties/viewers/BuffRemoverViewer.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template lang="html">
|
||||
<div class="buff-remover-viewer">
|
||||
<v-row dense>
|
||||
<property-field
|
||||
v-if="model.target === 'self'"
|
||||
name="Target"
|
||||
value="Self"
|
||||
/>
|
||||
<template v-if="!model.targetParentBuff">
|
||||
<property-field
|
||||
v-if="model.target === 'self'"
|
||||
name="Target"
|
||||
value="Self"
|
||||
/>
|
||||
<property-field
|
||||
name="When applied"
|
||||
:value="model.removeAll ? 'Remove all matching buffs' : 'Remove 1 matching buff'"
|
||||
/>
|
||||
<property-field
|
||||
name="Targeted tags"
|
||||
>
|
||||
<div>
|
||||
<div class="d-flex flex-wrap">
|
||||
<v-chip
|
||||
v-for="(tag, index) in model.targetTags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
</div>
|
||||
<div
|
||||
v-for="ex in model.extraTags"
|
||||
:key="ex._id"
|
||||
>
|
||||
<span class="ma-2">
|
||||
{{ ex.operation }}
|
||||
</span>
|
||||
<div class="d-flex flex-wrap">
|
||||
<v-chip
|
||||
v-for="(extraTag, index) in ex.tags"
|
||||
:key="index"
|
||||
class="ma-1"
|
||||
>
|
||||
{{ extraTag }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</property-field>
|
||||
</template>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyViewerMixin.js'
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
|
||||
export default {
|
||||
mixins: [propertyViewerMixin],
|
||||
computed: {
|
||||
reset(){
|
||||
let reset = this.model.reset
|
||||
if (reset === 'shortRest'){
|
||||
return `Reset${
|
||||
this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a short rest`;
|
||||
} else if (reset === 'longRest'){
|
||||
return `Reset${
|
||||
this.model.resetMultiplier && ' x' + this.model.resetMultiplier
|
||||
} on a long rest`;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberToSignedString,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.ability-value {
|
||||
font-weight: 600;
|
||||
font-size: 24px !important;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
.mod, .ability-value {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.attribute-value {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
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>
|
||||
@@ -47,6 +47,12 @@
|
||||
name="Passive score"
|
||||
:value="passiveScore"
|
||||
/>
|
||||
<property-field
|
||||
v-if="model.overridden"
|
||||
:cols="{cols: 6, md: 12}"
|
||||
name="Overridden"
|
||||
value="Overriden by another property with the same variable name"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<property-description
|
||||
@@ -55,7 +61,7 @@
|
||||
/>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-if="baseEffects.length || ability || effects.length"
|
||||
v-if="ability || (effects && effects.length)"
|
||||
dense
|
||||
>
|
||||
<property-field
|
||||
@@ -63,14 +69,6 @@
|
||||
name="Effects"
|
||||
>
|
||||
<v-list style="width: 100%">
|
||||
<attribute-effect
|
||||
v-for="effect in baseEffects"
|
||||
:key="effect._id === model._id ? 'this_base' : effect._id"
|
||||
:model="effect"
|
||||
:hide-breadcrumbs="effect._id === model._id"
|
||||
:data-id="effect._id"
|
||||
@click="effect._id !== model._id && clickEffect(effect._id)"
|
||||
/>
|
||||
<attribute-effect
|
||||
v-if="ability"
|
||||
:key="ability._id"
|
||||
@@ -188,32 +186,8 @@ export default {
|
||||
variables(){
|
||||
return CreatureVariables.findOne({_creatureId: this.context.creatureId}) || {};
|
||||
},
|
||||
baseEffects(){
|
||||
if (this.context.creatureId){
|
||||
let creatureId = this.context.creatureId;
|
||||
return CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
type: 'skill',
|
||||
variableName: this.model.variableName,
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}).map( prop => ({
|
||||
_id: prop._id,
|
||||
name: 'Skill base value',
|
||||
operation: 'base',
|
||||
calculation: prop.baseValueCalculation,
|
||||
amount: {value: prop.baseValue?.value},
|
||||
stats: [prop.variableName],
|
||||
ancestors: prop.ancestors,
|
||||
}) ).filter(effect => effect.amount?.value);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
effects() {
|
||||
return CreatureProperties.find({
|
||||
_id: { $in: this.model.effects?.map(e => e._id) || [] }
|
||||
});
|
||||
return this.model.effects;
|
||||
},
|
||||
baseProficiencies(){
|
||||
if (this.context.creatureId){
|
||||
@@ -265,7 +239,7 @@ export default {
|
||||
return {
|
||||
_id: abilityProp._id,
|
||||
name: abilityProp.name,
|
||||
operation: 'base',
|
||||
operation: 'add',
|
||||
amount: {value: abilityProp.modifier},
|
||||
stats: [this.model.variableName],
|
||||
ancestors: abilityProp.ancestors,
|
||||
|
||||
@@ -2,6 +2,7 @@ const ActionViewer = () => import ('/imports/ui/properties/viewers/ActionViewer.
|
||||
const AdjustmentViewer = () => import ('/imports/ui/properties/viewers/AdjustmentViewer.vue');
|
||||
const AttributeViewer = () => import ('/imports/ui/properties/viewers/AttributeViewer.vue');
|
||||
const BuffViewer = () => import ('/imports/ui/properties/viewers/BuffViewer.vue');
|
||||
const BuffRemoverViewer = () => import ('/imports/ui/properties/viewers/BuffRemoverViewer.vue');
|
||||
const BranchViewer = () => import ('/imports/ui/properties/viewers/BranchViewer.vue');
|
||||
const ContainerViewer = () => import ('/imports/ui/properties/viewers/ContainerViewer.vue');
|
||||
const ClassViewer = () => import ('/imports/ui/properties/viewers/ClassViewer.vue');
|
||||
@@ -14,6 +15,7 @@ const FeatureViewer = () => import ('/imports/ui/properties/viewers/FeatureViewe
|
||||
const FolderViewer = () => import ('/imports/ui/properties/viewers/FolderViewer.vue');
|
||||
const ItemViewer = () => import ('/imports/ui/properties/viewers/ItemViewer.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 ReferenceViewer = () => import ('/imports/ui/properties/viewers/ReferenceViewer.vue');
|
||||
const RollViewer = () => import ('/imports/ui/properties/viewers/RollViewer.vue');
|
||||
@@ -31,6 +33,7 @@ export default {
|
||||
adjustment: AdjustmentViewer,
|
||||
attribute: AttributeViewer,
|
||||
buff: BuffViewer,
|
||||
buffRemover: BuffRemoverViewer,
|
||||
branch: BranchViewer,
|
||||
container: ContainerViewer,
|
||||
class: ClassViewer,
|
||||
@@ -43,6 +46,7 @@ export default {
|
||||
folder: FolderViewer,
|
||||
item: ItemViewer,
|
||||
note: NoteViewer,
|
||||
pointBuy: PointBuyViewer,
|
||||
proficiency: ProficiencyViewer,
|
||||
propertySlot: SlotViewer,
|
||||
roll: RollViewer,
|
||||
|
||||
@@ -43,7 +43,7 @@ import TabletopMap from '/imports/ui/tabletop/TabletopMap.vue';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
import TabletopActionCards from '/imports/ui/tabletop/TabletopActionCards.vue';
|
||||
import MiniCharacterSheet from '/imports/ui/creature/character/MiniCharacterSheet.vue';
|
||||
import snackbar from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import dialogStackStore from '/imports/ui/dialogStack/dialogStackStore.js';
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
const tabs = ['stats', 'features', 'inventory', 'spells', 'journal', 'build', 'tree'];
|
||||
const tabsWithoutSpells = ['stats', 'features', 'inventory', 'journal', 'build', 'tree'];
|
||||
|
||||
Vue.use(Vuex);
|
||||
const store = new Vuex.Store({
|
||||
@@ -16,13 +19,21 @@ const store = new Vuex.Store({
|
||||
showDetailsDialog: false,
|
||||
},
|
||||
getters: {
|
||||
// ...
|
||||
tabById: (state) => (id) => {
|
||||
if (id in state.characterSheetTabs){
|
||||
return state.characterSheetTabs[id];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
tabNameById: (state) => (id) => {
|
||||
const tabNumber = state.characterSheetTabs[id];
|
||||
const creature = Creatures.findOne(id);
|
||||
if (creature?.settings?.hideSpellsTab) {
|
||||
return tabsWithoutSpells[tabNumber];
|
||||
} else {
|
||||
return tabs[tabNumber]
|
||||
}
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dicecloud",
|
||||
"version": "2.0.33",
|
||||
"version": "2.0.38",
|
||||
"description": "Unofficial Online Realtime D&D 5e App",
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
@@ -11,7 +11,8 @@
|
||||
"scripts": {
|
||||
"run": "meteor",
|
||||
"debug": "meteor --inspect",
|
||||
"test": "meteor test --driver-package meteortesting:mocha --port 3001"
|
||||
"test": "meteor test --driver-package meteortesting:mocha --port 3001",
|
||||
"build": "meteor build ../build --architecture os.linux.x86_64"
|
||||
},
|
||||
"engines": {
|
||||
"node": "14.0.x",
|
||||
|
||||
Reference in New Issue
Block a user