Merge branch 'version-2-dev' into version-2
This commit is contained in:
@@ -11,7 +11,7 @@ accounts-google@1.4.0
|
||||
email@2.2.0
|
||||
meteor-base@1.5.1
|
||||
mobile-experience@1.1.0
|
||||
mongo@1.14.0
|
||||
mongo@1.14.6
|
||||
session@1.2.0
|
||||
tracker@1.2.0
|
||||
logging@1.3.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@2.6
|
||||
METEOR@2.6.1
|
||||
|
||||
@@ -12,7 +12,7 @@ aldeed:collection2@3.5.0
|
||||
aldeed:schema-index@3.0.0
|
||||
allow-deny@1.1.1
|
||||
autoupdate@1.8.0
|
||||
babel-compiler@7.8.0
|
||||
babel-compiler@7.8.1
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
@@ -68,7 +68,7 @@ mobile-status-bar@1.1.0
|
||||
modern-browsers@0.1.7
|
||||
modules@0.18.0
|
||||
modules-runtime@0.12.0
|
||||
mongo@1.14.4
|
||||
mongo@1.14.6
|
||||
mongo-decimal@0.1.2
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.8
|
||||
@@ -121,6 +121,6 @@ tracker@1.2.0
|
||||
typescript@4.4.1
|
||||
underscore@1.0.10
|
||||
url@1.3.2
|
||||
webapp@1.13.0
|
||||
webapp@1.13.1
|
||||
webapp-hashing@1.1.0
|
||||
zer0th:meteor-vuetify-loader@0.1.41
|
||||
|
||||
@@ -120,6 +120,20 @@ let CreatureSchema = new SimpleSchema({
|
||||
blackbox: true,
|
||||
defaultValue: {}
|
||||
},
|
||||
computeErrors: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
},
|
||||
'computeErrors.$': {
|
||||
type: Object,
|
||||
},
|
||||
'computeErrors.$.type': {
|
||||
type: String,
|
||||
},
|
||||
'computeErrors.$.details' : {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
|
||||
// Tabletop
|
||||
tabletop: {
|
||||
|
||||
@@ -5,6 +5,7 @@ import applyProperty from '../applyProperty.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js';
|
||||
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
|
||||
export default function applyAction(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
@@ -24,16 +25,18 @@ export default function applyAction(node, {creature, targets, scope, log}){
|
||||
const failed = spendResources({prop, log, scope});
|
||||
if (failed) return;
|
||||
|
||||
const attack = prop.attackRoll || prop.attackRollBonus;
|
||||
|
||||
// Attack if there is an attack roll
|
||||
if (prop.attackRoll && prop.attackRoll.calculation){
|
||||
if (attack && attack.calculation){
|
||||
if (targets.length){
|
||||
targets.forEach(target => {
|
||||
applyAttackToTarget({prop, target, scope, log});
|
||||
applyAttackToTarget({attack, target, scope, log});
|
||||
// Apply the children, but only to the current target
|
||||
applyChildren(node, {targets: [target], scope, log});
|
||||
});
|
||||
} else {
|
||||
applyAttackWithoutTarget({prop, scope, log});
|
||||
applyAttackWithoutTarget({attack, scope, log});
|
||||
applyChildren(node, {creature, targets, scope, log});
|
||||
}
|
||||
} else {
|
||||
@@ -41,44 +44,34 @@ export default function applyAction(node, {creature, targets, scope, log}){
|
||||
}
|
||||
}
|
||||
|
||||
function applyAttackWithoutTarget({prop, scope, log}){
|
||||
function applyAttackWithoutTarget({attack, scope, log}){
|
||||
delete scope['$attackHit'];
|
||||
delete scope['$attackMiss'];
|
||||
delete scope['$criticalHit'];
|
||||
delete scope['$criticalMiss'];
|
||||
delete scope['$attackRoll'];
|
||||
|
||||
recalculateCalculation(prop.attackRoll, scope, log);
|
||||
recalculateCalculation(attack, scope, log);
|
||||
|
||||
let value = rollDice(1, 20)[0];
|
||||
scope['$attackRoll'] = {value};
|
||||
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
if (criticalHit){
|
||||
scope['$criticalHit'] = {value: true};
|
||||
scope['$attackHit'] = {value: true};
|
||||
} else {
|
||||
let criticalMiss = value === 1;
|
||||
if (criticalMiss){
|
||||
scope['$criticalMiss'] = 1;
|
||||
log.content.push({
|
||||
name: 'Critical Miss!',
|
||||
});
|
||||
scope['$attackMiss'] = {value: true};
|
||||
} else {
|
||||
// Untargeted attacks hit by default
|
||||
scope['$attackHit'] = {value: true}
|
||||
}
|
||||
let {
|
||||
resultPrefix,
|
||||
result,
|
||||
criticalHit,
|
||||
criticalMiss,
|
||||
} = rollAttack(attack, scope);
|
||||
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
|
||||
if (scope['$attackAdvantage'] === 1){
|
||||
name += ' (Advantage)';
|
||||
} else if(scope['$attackAdvantage'] === -1){
|
||||
name += ' (Disadvantage)';
|
||||
}
|
||||
let result = value + prop.attackRoll.value;
|
||||
scope['$attackRoll'] = {value: result};
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : 'To Hit',
|
||||
value: `1d20 [${value}] + ${prop.attackRoll.value} = ` + result,
|
||||
name,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
});
|
||||
}
|
||||
|
||||
function applyAttackToTarget({prop, target, scope, log}){
|
||||
function applyAttackToTarget({attack, target, scope, log}){
|
||||
delete scope['$attackHit'];
|
||||
delete scope['$attackMiss'];
|
||||
delete scope['$criticalHit'];
|
||||
@@ -86,26 +79,30 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
delete scope['$attackDiceRoll'];
|
||||
delete scope['$attackRoll'];
|
||||
|
||||
recalculateCalculation(prop.attackRoll, scope, log);
|
||||
recalculateCalculation(attack, scope, log);
|
||||
|
||||
let {
|
||||
resultPrefix,
|
||||
result,
|
||||
criticalHit,
|
||||
criticalMiss,
|
||||
} = rollAttack(attack, scope);
|
||||
|
||||
const value = rollDice(1, 20)[0];
|
||||
scope['$attackDiceRoll'] = {value};
|
||||
const criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
const criticalHit = value >= criticalHitTarget;
|
||||
const criticalMiss = value === 1;
|
||||
if (criticalHit) scope['$criticalHit'] = {value: true};
|
||||
if (criticalMiss) scope['$criticalMiss'] = {value: true};
|
||||
const result = value + prop.attackRoll.value;
|
||||
scope['$attackRoll'] = {value: result};
|
||||
if (target.variables.armor){
|
||||
const armor = target.variables.armor.value;
|
||||
const name = criticalHit ? 'Critical Hit!' :
|
||||
criticalMiss ? 'Critical miss!' :
|
||||
result > armor ? 'Hit!' :
|
||||
'Miss!'
|
||||
|
||||
let name = criticalHit ? 'Critical Hit!' :
|
||||
criticalMiss ? 'Critical Miss!' :
|
||||
result > armor ? 'Hit!' : 'Miss!';
|
||||
if (scope['$attackAdvantage'] === 1){
|
||||
name += ' (Advantage)';
|
||||
} else if(scope['$attackAdvantage'] === -1){
|
||||
name += ' (Disadvantage)';
|
||||
}
|
||||
|
||||
log.content.push({
|
||||
name,
|
||||
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
});
|
||||
if ((result > armor) || (criticalHit)){
|
||||
scope['$attackHit'] = true;
|
||||
@@ -118,12 +115,63 @@ function applyAttackToTarget({prop, target, scope, log}){
|
||||
value:'Target has no `armor`',
|
||||
});
|
||||
log.content.push({
|
||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit',
|
||||
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
|
||||
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function rollAttack(attack, scope){
|
||||
const rollModifierText = numberToSignedString(attack.value, true);
|
||||
let value, resultPrefix;
|
||||
if (attack.advantage === 1 || scope['$attackAdvantage']){
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (attack.advantage === -1 || scope['$attackDisadvantage']){
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else {
|
||||
value = rollDice(1, 20)[0];
|
||||
resultPrefix = `1d20 [${value}] ${rollModifierText} = `
|
||||
}
|
||||
scope['$attackRoll'] = {value};
|
||||
const result = value + attack.value;
|
||||
const {criticalHit, criticalMiss} = applyCrits(value, scope);
|
||||
return {resultPrefix, result, value, criticalHit, criticalMiss};
|
||||
}
|
||||
|
||||
function applyCrits(value, scope){
|
||||
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
|
||||
let criticalHit = value >= criticalHitTarget;
|
||||
let criticalMiss;
|
||||
if (criticalHit){
|
||||
scope['$criticalHit'] = {value: true};
|
||||
scope['$attackHit'] = {value: true};
|
||||
} else {
|
||||
criticalMiss = value === 1;
|
||||
if (criticalMiss){
|
||||
scope['$criticalMiss'] = 1;
|
||||
scope['$attackMiss'] = {value: true};
|
||||
} else {
|
||||
// Untargeted attacks hit by default
|
||||
scope['$attackHit'] = {value: true}
|
||||
}
|
||||
}
|
||||
return {criticalHit, criticalMiss};
|
||||
}
|
||||
|
||||
function applyChildren(node, args){
|
||||
node.children.forEach(child => applyProperty(child, args));
|
||||
}
|
||||
|
||||
@@ -88,16 +88,16 @@ export default function applyDamage(node, {
|
||||
// Log the damage done
|
||||
if (target._id === creature._id){
|
||||
// Target is same as self, log damage as such
|
||||
logValue.push(damageDealt + suffix + ' to self');
|
||||
logValue.push(`**${damageDealt}** ${suffix} to self`);
|
||||
} else {
|
||||
logValue.push('Dealt ' + damageDealt + suffix + ` ${target.name && ' to '}${target.name}`);
|
||||
logValue.push(`Dealt **${damageDealt}** ${suffix} ${target.name && ' to '}${target.name}`);
|
||||
// Log the damage received on that creature's log as well
|
||||
insertCreatureLog.call({
|
||||
log: {
|
||||
creatureId: target._id,
|
||||
content: [{
|
||||
name,
|
||||
value: 'Recieved ' + damageDealt + suffix,
|
||||
value: `Recieved **${damageDealt}** ${suffix}`,
|
||||
}],
|
||||
}
|
||||
});
|
||||
@@ -105,7 +105,7 @@ export default function applyDamage(node, {
|
||||
});
|
||||
} else {
|
||||
// There are no targets, just log the result
|
||||
logValue.push(damage + suffix);
|
||||
logValue.push(`**${damage}** ${suffix}`);
|
||||
}
|
||||
log.content.push({
|
||||
name: logName,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
import recalculateCalculation from './shared/recalculateCalculation.js';
|
||||
import applyProperty from '../applyProperty.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
|
||||
export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||
const prop = node.node;
|
||||
@@ -46,19 +47,31 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
|
||||
return applyChildren();
|
||||
}
|
||||
|
||||
const rollModifierText = numberToSignedString(save.value, true);
|
||||
|
||||
let value, values, resultPrefix;
|
||||
if (save.advantage === 1){
|
||||
values = rollDice(2, 20).sort().reverse();
|
||||
value = values[0];
|
||||
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (save.advantage === -1){
|
||||
values = rollDice(2, 20).sort();
|
||||
value = values[0];
|
||||
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `Disadvantage: 1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `Disadvantage: 1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else {
|
||||
values = rollDice(1, 20);
|
||||
value = values[0];
|
||||
resultPrefix = `1d20 [${value}] + ${save.value} = `
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||
}
|
||||
scope['$saveDiceRoll'] = {value};
|
||||
const result = value + save.value || 0;
|
||||
|
||||
@@ -24,13 +24,18 @@ const doAction = new ValidatedMethod({
|
||||
type: String,
|
||||
regEx: SimpleSchema.RegEx.Id,
|
||||
},
|
||||
scope: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
optional: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({actionId, targetIds = []}) {
|
||||
run({actionId, targetIds = [], scope}) {
|
||||
let action = CreatureProperties.findOne(actionId);
|
||||
// Check permissions
|
||||
let creature = getRootCreatureAncestor(action);
|
||||
@@ -69,7 +74,7 @@ const doAction = new ValidatedMethod({
|
||||
});
|
||||
|
||||
// Do the action
|
||||
doActionWork({creature, targets, properties, ancestors, method: this});
|
||||
doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope});
|
||||
|
||||
// Recompute all involved creatures
|
||||
computeCreature(creature._id);
|
||||
@@ -82,7 +87,7 @@ const doAction = new ValidatedMethod({
|
||||
export default doAction;
|
||||
|
||||
export function doActionWork({
|
||||
creature, targets, properties, ancestors, method
|
||||
creature, targets, properties, ancestors, method, methodScope = {}
|
||||
}){
|
||||
// get the docs
|
||||
const ancestorScope = getAncestorScope(ancestors);
|
||||
@@ -102,6 +107,7 @@ export function doActionWork({
|
||||
const scope = {
|
||||
...creature.variables,
|
||||
...ancestorScope,
|
||||
...methodScope
|
||||
}
|
||||
applyProperty(propertyForest[0], {
|
||||
creature,
|
||||
|
||||
114
app/imports/api/engine/actions/doCheck.js
Normal file
114
app/imports/api/engine/actions/doCheck.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
||||
import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js';
|
||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import { CreatureLogSchema, insertCreatureLogWork } from '/imports/api/creature/log/CreatureLogs.js';
|
||||
import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js';
|
||||
import computeCreature from '/imports/api/engine/computeCreature.js';
|
||||
import rollDice from '/imports/parser/rollDice.js';
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
|
||||
const doCheck = new ValidatedMethod({
|
||||
name: 'creatureProperties.doCheck',
|
||||
validate: new SimpleSchema({
|
||||
propId: SimpleSchema.RegEx.Id,
|
||||
scope: {
|
||||
type: Object,
|
||||
blackbox: true,
|
||||
},
|
||||
}).validator(),
|
||||
mixins: [RateLimiterMixin],
|
||||
rateLimit: {
|
||||
numRequests: 10,
|
||||
timeInterval: 5000,
|
||||
},
|
||||
run({propId, scope}) {
|
||||
const prop = CreatureProperties.findOne(propId);
|
||||
const creature = getRootCreatureAncestor(prop);
|
||||
|
||||
// Check permissions
|
||||
assertEditPermission(creature, this.userId);
|
||||
|
||||
// Do the check
|
||||
doCheckWork({creature, prop, method: this, methodScope: scope});
|
||||
|
||||
// Recompute all involved creatures
|
||||
computeCreature(creature._id);
|
||||
},
|
||||
});
|
||||
|
||||
export default doCheck;
|
||||
|
||||
export function doCheckWork({
|
||||
creature, prop, method, methodScope = {}
|
||||
}){
|
||||
// Create the log
|
||||
let log = CreatureLogSchema.clean({
|
||||
creatureId: creature._id,
|
||||
creatureName: creature.name,
|
||||
});
|
||||
|
||||
rollCheck({prop, log, methodScope});
|
||||
|
||||
// Insert the log
|
||||
insertCreatureLogWork({log, creature, method});
|
||||
}
|
||||
|
||||
function rollCheck({prop, log, methodScope}){
|
||||
// get the modifier for the roll
|
||||
let rollModifier;
|
||||
let logName = `${prop.name} check`;
|
||||
if (prop.type === 'skill'){
|
||||
rollModifier = prop.value;
|
||||
if (prop.skillType === 'save'){
|
||||
if (prop.name.match(/save/i)){
|
||||
logName = prop.name;
|
||||
} else {
|
||||
logName = prop.name ? `${prop.name} save` : 'Saving Throw';
|
||||
}
|
||||
}
|
||||
} else if (prop.type === 'attribute'){
|
||||
if (prop.attributeType === 'ability'){
|
||||
rollModifier = prop.modifier;
|
||||
} else {
|
||||
rollModifier = prop.value;
|
||||
}
|
||||
} else {
|
||||
throw (`${prop.type} not supported for checks`);
|
||||
}
|
||||
|
||||
const rollModifierText = numberToSignedString(rollModifier, true);
|
||||
|
||||
let value, values, resultPrefix;
|
||||
if (methodScope['$checkAdvantage'] === 1){
|
||||
logName += ' (Advantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a >= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else if (methodScope['$checkAdvantage'] === -1){
|
||||
logName += ' (Disadvantage)';
|
||||
const [a, b] = rollDice(2, 20);
|
||||
if (a <= b) {
|
||||
value = a;
|
||||
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] ${rollModifierText} = `;
|
||||
} else {
|
||||
value = b;
|
||||
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText} = `;
|
||||
}
|
||||
} else {
|
||||
values = rollDice(1, 20);
|
||||
value = values[0];
|
||||
resultPrefix = `1d20 [ ${value} ] ${rollModifierText} = `
|
||||
}
|
||||
const result = (value + rollModifier) || 0;
|
||||
log.content.push({
|
||||
name: logName,
|
||||
value: `${resultPrefix} **${result}**`,
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export default class CreatureComputation {
|
||||
this.scope = {};
|
||||
this.props = properties;
|
||||
this.dependencyGraph = createGraph();
|
||||
this.errors = [];
|
||||
|
||||
// Store properties for easy access later
|
||||
properties.forEach(prop => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function aggregateClassLevel({node, linkedNode, link}){
|
||||
if (node.data.inactive) return;
|
||||
if (link.data === 'classLevel'){
|
||||
if (node.data.inactive) return;
|
||||
if (!node.data.classLevelAggregator) node.data.classLevelAggregator = {
|
||||
levelsFilled: [true], // Level 0 is always filled
|
||||
level: 0,
|
||||
@@ -11,6 +11,6 @@ export default function aggregateClassLevel({node, linkedNode, link}){
|
||||
aggregator.levelsFilled[linkedProp.level] = true;
|
||||
} else if (link.data === 'level'){
|
||||
node.data.baseValue = (node.data.baseValue || 0) +
|
||||
linkedNode.data.classLevelAggregator.level;
|
||||
(linkedNode.data.classLevelAggregator?.level || 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import computeToggles from '/imports/api/engine/computation/computeComputation/computeToggles.js';
|
||||
import computeByType from '/imports/api/engine/computation/computeComputation/computeByType.js';
|
||||
import embedInlineCalculations from './utility/embedInlineCalculations.js';
|
||||
import path from 'ngraph.path';
|
||||
|
||||
export default function computeCreatureComputation(computation){
|
||||
const stack = [];
|
||||
@@ -12,6 +13,12 @@ export default function computeCreatureComputation(computation){
|
||||
node._visitedChildren = false;
|
||||
stack.push(node)
|
||||
});
|
||||
|
||||
// The graph nodes in the stack are ordered, by reversing the order we
|
||||
// compute higher nodes in the tree first, which for dep loops is more likely
|
||||
// to be a good guess of where to start thant the inverse
|
||||
stack.reverse();
|
||||
|
||||
// Depth first traversal of nodes
|
||||
while (stack.length){
|
||||
let top = stack[stack.length - 1];
|
||||
@@ -27,7 +34,7 @@ export default function computeCreatureComputation(computation){
|
||||
} else {
|
||||
top._visitedChildren = true;
|
||||
// Push dependencies to graph to be computed first
|
||||
pushDependenciesToStack(top.id, graph, stack);
|
||||
pushDependenciesToStack(top.id, graph, stack, computation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +49,20 @@ function compute(computation, node){
|
||||
computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
}
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack){
|
||||
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);
|
||||
computation.errors.push({
|
||||
type: 'dependencyLoop',
|
||||
details: {
|
||||
nodes: loop.map(node => node.id)
|
||||
},
|
||||
});
|
||||
}
|
||||
stack.push(linkedNode);
|
||||
}, true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
||||
|
||||
export default function(creatureId, errors = []){
|
||||
if (errors.length){
|
||||
Creatures.update(creatureId, {$set: {computeErrors: errors}});
|
||||
} else {
|
||||
Creatures.update(creatureId, {$unset: {computeErrors: 1}});
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,14 @@ import buildCreatureComputation from './computation/buildCreatureComputation.js'
|
||||
import computeCreatureComputation from './computation/computeCreatureComputation.js';
|
||||
import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js';
|
||||
import writeScope from './computation/writeComputation/writeScope.js';
|
||||
import writeErrors from './computation/writeComputation/writeErrors.js';
|
||||
|
||||
export default function computeCreature(creatureId){
|
||||
const computation = buildCreatureComputation(creatureId);
|
||||
computeCreatureComputation(computation);
|
||||
writeAlteredProperties(computation);
|
||||
writeScope(creatureId, computation.scope);
|
||||
writeErrors(creatureId, computation.errors);
|
||||
}
|
||||
|
||||
// For now just recompute the whole creature, TODO only recompute a single
|
||||
|
||||
@@ -47,7 +47,7 @@ function computedOnlyInlineCalculationField(field){
|
||||
},
|
||||
[`${field}.inlineCalculations.$`]: {
|
||||
type: Object,
|
||||
parseLevel: 'compile',
|
||||
parseLevel: 'reduce',
|
||||
computedField: true,
|
||||
},
|
||||
// The part between bracers {}
|
||||
|
||||
@@ -217,7 +217,9 @@ function getInlineComputationTransforms(key){
|
||||
|
||||
function calculationUp(val){
|
||||
if (typeof val !== 'string') return val;
|
||||
return val.replace('.value', '.total').replace('.currentValue', '.value');
|
||||
return val.replace(/#(\w+).(\w+)Result/g, '#$1.$2')
|
||||
.replace('.value', '.total')
|
||||
.replace('.currentValue', '.value');
|
||||
}
|
||||
|
||||
function calculationDown(val){
|
||||
|
||||
@@ -16,7 +16,7 @@ const rollArray = {
|
||||
};
|
||||
},
|
||||
toString(node){
|
||||
return `${node.diceNum || ''}d${node.diceSize} [${node.values.join(', ')}]`;
|
||||
return `${node.diceNum || ''}d${node.diceSize} [ ${node.values.join(', ')} ]`;
|
||||
},
|
||||
reduce(node, scope, context){
|
||||
const total = node.values.reduce((a, b) => a + b, 0);
|
||||
|
||||
46
app/imports/ui/components/HorizontalHex.vue
Normal file
46
app/imports/ui/components/HorizontalHex.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="d-flex justify-center align-center"
|
||||
style="height: 120px; width: 120px;"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
style="height: 120px; width: 120px; position: absolute;"
|
||||
>
|
||||
<path
|
||||
d="M 251.313 23.844 L 49.438 140.25 L 49.062 373.75 L 251.687 490.156 L 453.563 373.75 L 453.938 140.25 L 251.313 23.844 Z"
|
||||
fill-opacity="1"
|
||||
:fill="hover ? 'rgb(255, 255, 255)' : 'rgb(255, 255, 255)'"
|
||||
/>
|
||||
<path
|
||||
d="M 249.801 51.001 L 71.808 153.637 L 71.477 359.513 L 250.131 462.148 L 428.125 359.513 L 428.455 153.637 L 249.801 51.001 Z"
|
||||
fill-opacity="1"
|
||||
:fill="hover ? 'rgb(40, 40, 40)' : 'rgb(80, 80, 80)'"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
v-ripple
|
||||
style="height: 100px; width: 100px; border-radius: 50%; z-index: 1; cursor: pointer;"
|
||||
class="d-flex justify-center align-center"
|
||||
@mouseover="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
data(){return {
|
||||
hover: false,
|
||||
}},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
path {
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
118
app/imports/ui/components/RollPopup.vue
Normal file
118
app/imports/ui/components/RollPopup.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template lang="html">
|
||||
<div>
|
||||
<v-menu
|
||||
v-model="open"
|
||||
origin="center center"
|
||||
transition="scale-transition"
|
||||
nudge-left="100px"
|
||||
nudge-top="100px"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
v-bind="$attrs"
|
||||
:class="buttonClass"
|
||||
v-on="on"
|
||||
@click.stop
|
||||
>
|
||||
<slot />
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-sheet class="d-flex flex-column align-center justify-center">
|
||||
<v-btn-toggle v-model="dataAdvantage">
|
||||
<v-btn :value="-1">
|
||||
Disadvantage
|
||||
</v-btn>
|
||||
<v-btn :value="1">
|
||||
Advantage
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<div class="ma-1 text-subtitle-2">
|
||||
{{ name }}
|
||||
</div>
|
||||
<div>
|
||||
<v-scale-transition
|
||||
origin="center center"
|
||||
>
|
||||
<vertical-hex
|
||||
v-if="dataAdvantage"
|
||||
style="position:absolute; transition: margin-left 0.3s ease;"
|
||||
:style="{marginLeft: dataAdvantage == 1 ? '24px' : '-24px'}"
|
||||
disable-hover
|
||||
/>
|
||||
</v-scale-transition>
|
||||
<vertical-hex @click="roll">
|
||||
<div>
|
||||
Roll
|
||||
</div>
|
||||
<div v-if="rollText">
|
||||
{{ rollText }}
|
||||
</div>
|
||||
</vertical-hex>
|
||||
</div>
|
||||
<v-btn
|
||||
text
|
||||
color="primary"
|
||||
style="align-self: end"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
</v-sheet>
|
||||
</v-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
import VerticalHex from '/imports/ui/components/VerticalHex.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
VerticalHex
|
||||
},
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
rollText: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
buttonClass: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
advantage: {
|
||||
type: Number,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
open: false,
|
||||
dataAdvantage: this.advantage,
|
||||
}},
|
||||
watch: {
|
||||
advantage(val){
|
||||
this.dataAdvantage = val;
|
||||
},
|
||||
open(val){
|
||||
if(!val){
|
||||
this.dataAdvantage = this.advantage;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
roll(){
|
||||
this.$emit('roll', {advantage: this.dataAdvantage});
|
||||
this.open = false;
|
||||
},
|
||||
close(){
|
||||
this.open = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
</style>
|
||||
68
app/imports/ui/components/VerticalHex.vue
Normal file
68
app/imports/ui/components/VerticalHex.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template lang="html">
|
||||
<div
|
||||
class="d-flex justify-center align-center"
|
||||
:style="`height: ${height}px; width: ${width}px;`"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
style="height: 120px; width: 120px; position: absolute;"
|
||||
>
|
||||
<path
|
||||
d="M 251.313 23.844 L 49.438 140.25 L 49.062 373.75 L 251.687 490.156 L 453.563 373.75 L 453.938 140.25 L 251.313 23.844 Z"
|
||||
fill-opacity="1"
|
||||
:fill="hover ? '#f44336 ' : '#f44336 '"
|
||||
/>
|
||||
<path
|
||||
d="M 249.801 51.001 L 71.808 153.637 L 71.477 359.513 L 250.131 462.148 L 428.125 359.513 L 428.455 153.637 L 249.801 51.001 Z"
|
||||
fill-opacity="1"
|
||||
:fill="theme.isDark ?
|
||||
hover ? 'rgb(80, 80, 80)' : 'rgb(40, 40, 40)':
|
||||
hover ? 'rgb(180, 180, 180)' : 'rgb(220, 220, 220)'
|
||||
"
|
||||
/>
|
||||
</svg>
|
||||
<div
|
||||
v-ripple
|
||||
style="height: 100px; width: 100px; border-radius: 50%; z-index: 1; cursor: pointer;"
|
||||
class="d-flex flex-column justify-center align-center"
|
||||
@mouseover="hover = !disableHover"
|
||||
@mouseleave="hover = false"
|
||||
@click="e => $emit('click', e)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="js">
|
||||
export default {
|
||||
inject: {
|
||||
theme: {
|
||||
default: {
|
||||
isDark: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: Number,
|
||||
default: 120,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 120,
|
||||
},
|
||||
disableHover: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
hover: false,
|
||||
}},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
path {
|
||||
transition: fill 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
@@ -399,6 +399,9 @@
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
doCheckLoading: false,
|
||||
}},
|
||||
meteor: {
|
||||
creature(){
|
||||
return Creatures.findOne(this.creatureId);
|
||||
|
||||
@@ -5,7 +5,30 @@
|
||||
>
|
||||
<div class="layout align-center px-3">
|
||||
<div class="avatar">
|
||||
<roll-popup
|
||||
v-if="rollBonus"
|
||||
icon
|
||||
outlined
|
||||
style="font-size: 16px; letter-spacing: normal;"
|
||||
class="mr-2"
|
||||
:color="model.color || 'primary'"
|
||||
:loading="doActionLoading"
|
||||
:disabled="model.insufficientResources || !context.editPermission"
|
||||
:roll-text="rollBonus"
|
||||
:name="model.name"
|
||||
:advantage="model.attackRoll && model.attackRoll.advantage"
|
||||
@roll="doAction"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
/>
|
||||
</roll-popup>
|
||||
<v-btn
|
||||
v-else
|
||||
icon
|
||||
outlined
|
||||
style="font-size: 16px; letter-spacing: normal;"
|
||||
@@ -15,11 +38,7 @@
|
||||
:disabled="model.insufficientResources || !context.editPermission"
|
||||
@click.stop="doAction"
|
||||
>
|
||||
<template v-if="rollBonus && !rollBonusTooLong">
|
||||
{{ rollBonus }}
|
||||
</template>
|
||||
<property-icon
|
||||
v-else
|
||||
:model="model"
|
||||
/>
|
||||
</v-btn>
|
||||
@@ -85,7 +104,9 @@ import doAction from '/imports/api/engine/actions/doAction.js';
|
||||
import AttributeConsumedView from '/imports/ui/properties/components/actions/AttributeConsumedView.vue';
|
||||
import ItemConsumedView from '/imports/ui/properties/components/actions/ItemConsumedView.vue';
|
||||
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import MarkdownText from '/imports/ui/components/MarkdownText.vue';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -93,6 +114,7 @@ export default {
|
||||
ItemConsumedView,
|
||||
MarkdownText,
|
||||
PropertyIcon,
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
@@ -131,7 +153,7 @@ export default {
|
||||
'theme--dark': this.theme.isDark,
|
||||
'theme--light': !this.theme.isDark,
|
||||
'muted-text': this.model.insufficientResources,
|
||||
'shrink': this.activated,
|
||||
'active': this.activated,
|
||||
'elevation-8': this.hovering,
|
||||
}
|
||||
},
|
||||
@@ -143,13 +165,19 @@ export default {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
doAction(){
|
||||
doAction({advantage}){
|
||||
this.doActionLoading = true;
|
||||
this.shwing();
|
||||
doAction.call({actionId: this.model._id}, error => {
|
||||
doAction.call({
|
||||
actionId: this.model._id,
|
||||
scope: {
|
||||
$attackAdvantage: advantage,
|
||||
}
|
||||
}, error => {
|
||||
this.doActionLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -157,7 +185,7 @@ export default {
|
||||
this.activated = true;
|
||||
setTimeout(() => {
|
||||
this.activated = undefined;
|
||||
}, 300);
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,7 +193,11 @@ export default {
|
||||
|
||||
<style lang="css" scoped>
|
||||
.action-card {
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
|
||||
transform 0.075s ease;
|
||||
}
|
||||
.action-card.active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
.action-title {
|
||||
font-size: 16px;
|
||||
|
||||
@@ -1,32 +1,46 @@
|
||||
<template lang="html">
|
||||
<v-list-item
|
||||
class="ability-list-tile"
|
||||
class="ability-list-tile pl-0"
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<v-list-item-action
|
||||
class="mr-4"
|
||||
class="ma-0"
|
||||
style="min-width: 40px;"
|
||||
>
|
||||
<div class="text-h4 mod">
|
||||
<template v-if="swapScoresAndMods">
|
||||
<span :class="{'primary--text': model.total !== model.value}">
|
||||
{{ model.value }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ numberToSignedString(model.modifier) }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-h6 value">
|
||||
<template v-if="swapScoresAndMods">
|
||||
{{ numberToSignedString(model.modifier) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :class="{'primary--text': model.total !== model.value}">
|
||||
{{ model.value }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<roll-popup
|
||||
button-class="mr-4 py-2"
|
||||
text
|
||||
height="82"
|
||||
:roll-text="numberToSignedString(model.modifier)"
|
||||
:name="model.name"
|
||||
:advantage="model.advantage"
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
@roll="check"
|
||||
>
|
||||
<div>
|
||||
<div class="text-h4 mod">
|
||||
<template v-if="swapScoresAndMods">
|
||||
<span :class="{'primary--text': model.total !== model.value}">
|
||||
{{ model.value }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ numberToSignedString(model.modifier) }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="text-h6 value">
|
||||
<template v-if="swapScoresAndMods">
|
||||
{{ numberToSignedString(model.modifier) }}
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :class="{'primary--text': model.total !== model.value}">
|
||||
{{ model.value }}
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</roll-popup>
|
||||
</v-list-item-action>
|
||||
|
||||
<v-list-item-content>
|
||||
@@ -39,10 +53,25 @@
|
||||
|
||||
<script lang="js">
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import doCheck from '/imports/api/engine/actions/doCheck.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {type: Object, required: true},
|
||||
},
|
||||
data(){return {
|
||||
checkLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && this.$listeners.click
|
||||
@@ -53,6 +82,21 @@ export default {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
check({advantage}){
|
||||
this.checkLoading = true;
|
||||
doCheck.call({
|
||||
propId: this.model._id,
|
||||
scope: {
|
||||
$checkAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
this.checkLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
meteor: {
|
||||
swapScoresAndMods(){
|
||||
@@ -86,5 +130,6 @@ export default {
|
||||
.mod, .value {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
min-width: 42px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,27 @@
|
||||
@click="click"
|
||||
>
|
||||
<div class="layout align-center">
|
||||
<v-card-title class="value text-h4 flex-shrink-0">
|
||||
<roll-popup
|
||||
v-if="model.attributeType === 'modifier' || model.type === 'skill'"
|
||||
button-class="px-0"
|
||||
text
|
||||
height="70"
|
||||
min-width="72"
|
||||
:roll-text="computedValue && computedValue.toString()"
|
||||
:name="model.name"
|
||||
:advantage="model.advantage"
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
@roll="check"
|
||||
>
|
||||
<v-card-title class="value text-h4 flex-shrink-0">
|
||||
{{ computedValue }}
|
||||
</v-card-title>
|
||||
</roll-popup>
|
||||
<v-card-title
|
||||
v-else
|
||||
class="value text-h4 flex-shrink-0"
|
||||
>
|
||||
{{ computedValue }}
|
||||
</v-card-title>
|
||||
<v-card-title class="name text-subtitle-1 text-truncate d-block pl-0">
|
||||
@@ -16,13 +36,28 @@
|
||||
|
||||
<script lang="js">
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import doCheck from '/imports/api/engine/actions/doCheck.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data(){return {
|
||||
checkLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
hasClickListener(){
|
||||
return this.$listeners && !!this.$listeners.click
|
||||
@@ -40,13 +75,28 @@
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
check({advantage}){
|
||||
this.checkLoading = true;
|
||||
doCheck.call({
|
||||
propId: this.model._id,
|
||||
scope: {
|
||||
$checkAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
this.checkLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.value {
|
||||
min-width: 64px;
|
||||
min-width: 72px;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
<v-list-item-title v-if="Number.isFinite(model.total)">
|
||||
<div
|
||||
v-if="model.total > 4"
|
||||
class="layout value"
|
||||
@@ -36,6 +36,11 @@
|
||||
</v-icon>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
<v-list-item-title v-else>
|
||||
<code>
|
||||
{{model.total}}
|
||||
</code>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ model.name }}
|
||||
</v-list-item-subtitle>
|
||||
|
||||
@@ -1,40 +1,57 @@
|
||||
<template lang="html">
|
||||
<v-list-item
|
||||
class="skill-list-tile"
|
||||
class="skill-list-tile pl-0"
|
||||
style="min-height: 36px;"
|
||||
v-on="hasClickListener ? {click} : {}"
|
||||
>
|
||||
<proficiency-icon
|
||||
:value="model.proficiency"
|
||||
class="prof-icon"
|
||||
/>
|
||||
<v-list-item-content class="py-1">
|
||||
<v-list-item-title>
|
||||
<span
|
||||
<v-list-item-content class="py-0">
|
||||
<v-list-item-title class="d-flex align-center">
|
||||
<roll-popup
|
||||
v-if="!hideModifier"
|
||||
class="prof-mod"
|
||||
class="prof-mod mr-1"
|
||||
button-class="pl-3 pr-2"
|
||||
text
|
||||
:roll-text="displayedModifier"
|
||||
:name="model.name"
|
||||
:advantage="model.advantage"
|
||||
:loading="checkLoading"
|
||||
:disabled="!context.editPermission"
|
||||
@roll="check"
|
||||
>
|
||||
{{ displayedModifier }}
|
||||
</span>
|
||||
<v-icon
|
||||
v-if="model.advantage > 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-up
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-if="model.advantage < 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-down
|
||||
</v-icon>
|
||||
{{ model.name }}
|
||||
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
|
||||
*
|
||||
</template>
|
||||
<template v-if="'passiveBonus' in model">
|
||||
({{ passiveScore }})
|
||||
</template>
|
||||
<proficiency-icon
|
||||
:value="model.proficiency"
|
||||
class="prof-icon"
|
||||
/>
|
||||
<div class="prof-mod">
|
||||
{{ displayedModifier }}
|
||||
</div>
|
||||
<v-icon
|
||||
v-if="model.advantage > 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-up
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-if="model.advantage < 0"
|
||||
size="20px"
|
||||
>
|
||||
mdi-chevron-double-down
|
||||
</v-icon>
|
||||
</roll-popup>
|
||||
<proficiency-icon
|
||||
v-else
|
||||
:value="model.proficiency"
|
||||
class="prof-icon ml-3 mr-2"
|
||||
/>
|
||||
<div>
|
||||
{{ model.name }}
|
||||
<template v-if="model.conditionalBenefits && model.conditionalBenefits.length">
|
||||
*
|
||||
</template>
|
||||
<template v-if="'passiveBonus' in model">
|
||||
({{ passiveScore }})
|
||||
</template>
|
||||
</div>
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
@@ -43,10 +60,19 @@
|
||||
<script lang="js">
|
||||
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
|
||||
import ProficiencyIcon from '/imports/ui/properties/shared/ProficiencyIcon.vue';
|
||||
import RollPopup from '/imports/ui/components/RollPopup.vue';
|
||||
import doCheck from '/imports/api/engine/actions/doCheck.js';
|
||||
import {snackbar} from '/imports/ui/components/snackbars/SnackbarQueue.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProficiencyIcon,
|
||||
RollPopup,
|
||||
},
|
||||
inject: {
|
||||
context: {
|
||||
default: {},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
model: {
|
||||
@@ -55,6 +81,9 @@ export default {
|
||||
},
|
||||
hideModifier: Boolean,
|
||||
},
|
||||
data(){return {
|
||||
checkLoading: false,
|
||||
}},
|
||||
computed: {
|
||||
displayedModifier(){
|
||||
let mod = this.model.value;
|
||||
@@ -75,6 +104,21 @@ export default {
|
||||
click(e){
|
||||
this.$emit('click', e);
|
||||
},
|
||||
check({advantage}){
|
||||
this.checkLoading = true;
|
||||
doCheck.call({
|
||||
propId: this.model._id,
|
||||
scope: {
|
||||
$checkAdvantage: advantage,
|
||||
},
|
||||
}, error => {
|
||||
this.checkLoading = false;
|
||||
if (error){
|
||||
console.error(error);
|
||||
snackbar({text: error.reason});
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -84,8 +128,9 @@ export default {
|
||||
min-width: 30px;
|
||||
}
|
||||
.prof-mod {
|
||||
display: inline-block;
|
||||
width: 45px;
|
||||
text-align: center;
|
||||
min-width: 32px;
|
||||
}
|
||||
.v-icon.theme--light {
|
||||
color: rgba(0, 0, 0, 0.54) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
name="Skill type"
|
||||
:value="skillTypes[model.skillType]"
|
||||
/>
|
||||
<property-field
|
||||
v-if="'passiveBonus' in model"
|
||||
name="Passive score"
|
||||
:value="passiveScore"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<property-description
|
||||
@@ -164,6 +169,9 @@ export default {
|
||||
icon(){
|
||||
return getProficiencyIcon(this.model.proficiency);
|
||||
},
|
||||
passiveScore(){
|
||||
return 10 + this.model.value + this.model.passiveBonus;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
numberToSignedString,
|
||||
@@ -207,6 +215,7 @@ export default {
|
||||
stats: this.model.variableName,
|
||||
type: 'effect',
|
||||
removed: {$ne: true},
|
||||
inactive: {$ne: true},
|
||||
}).fetch();
|
||||
} else {
|
||||
return [];
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export default function numberToSignedString(number){
|
||||
export default function numberToSignedString(number, spaced){
|
||||
if (typeof number !== 'number') return number;
|
||||
if (number === 0){
|
||||
return '+0';
|
||||
return spaced ? '+ 0' : '+0';
|
||||
} else if (number > 0){
|
||||
return `+${number}`;
|
||||
return spaced ? `+ ${number}` : `+${number}`;
|
||||
} else {
|
||||
return `${number}`;
|
||||
return spaced ? `- ${Math.abs(number) || number}` : `${number}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import Vuetify from 'vuetify/lib';
|
||||
import { Scroll } from 'vuetify/lib/directives';
|
||||
import { Scroll, Ripple } from 'vuetify/lib/directives';
|
||||
import SVG_ICONS from '/imports/constants/SVG_ICONS.js';
|
||||
import SvgIconByName from '/imports/ui/icons/SvgIconByName.vue';
|
||||
import themes from '/imports/ui/themes.js';
|
||||
@@ -9,6 +9,7 @@ import minifyTheme from 'minify-css-string';
|
||||
Vue.use(Vuetify, {
|
||||
directives: {
|
||||
Scroll,
|
||||
Ripple,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
9
app/package-lock.json
generated
9
app/package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dicecloud",
|
||||
"version": "0.10.0",
|
||||
"version": "2.0.33",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -2399,6 +2399,11 @@
|
||||
"ngraph.events": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"ngraph.path": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ngraph.path/-/ngraph.path-1.4.0.tgz",
|
||||
"integrity": "sha512-yJZay4tP0wcjqkkf8zlMQ/T+JOgU+EWfdE4w4TG8OS94B12J/+Z44UOYxVJErE8E6/wFunX1hMZEB1/GHsBYHg=="
|
||||
},
|
||||
"node-addon-api": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.1.0.tgz",
|
||||
@@ -2775,7 +2780,7 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": false,
|
||||
"resolved": "",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"simpl-schema": {
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"moo": "^0.5.1",
|
||||
"nearley": "^2.19.1",
|
||||
"ngraph.graph": "^19.1.0",
|
||||
"ngraph.path": "^1.4.0",
|
||||
"qrcode": "^1.5.0",
|
||||
"request": "^2.88.2",
|
||||
"simpl-schema": "^1.12.0",
|
||||
|
||||
@@ -9,6 +9,7 @@ import '/imports/server/publications/index.js';
|
||||
import '/imports/server/cron/deleteSoftRemovedDocuments.js';
|
||||
import '/imports/api/parenting/organizeMethods.js';
|
||||
import '/imports/api/users/patreon/updatePatreonOnLogin.js';
|
||||
import '/imports/api/engine/actions/doCheck.js';
|
||||
import '/imports/migrations/server/index.js';
|
||||
import '/imports/migrations/methods/index.js'
|
||||
import '/imports/constants/MAINTENANCE_MODE.js';
|
||||
|
||||
Reference in New Issue
Block a user