Skills can now apply to calcs by tag
This commit is contained in:
@@ -306,7 +306,7 @@ function linkSavingThrow(dependencyGraph, prop) {
|
|||||||
dependOnCalc({ dependencyGraph, prop, key: 'dc' });
|
dependOnCalc({ dependencyGraph, prop, key: 'dc' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkSkill(dependencyGraph, prop) {
|
function linkSkill(dependencyGraph, prop, computation) {
|
||||||
// Depends on base value
|
// Depends on base value
|
||||||
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
||||||
// Link dependents
|
// Link dependents
|
||||||
@@ -318,6 +318,20 @@ function linkSkill(dependencyGraph, prop) {
|
|||||||
}
|
}
|
||||||
// Skills depend on the creature's proficiencyBonus
|
// Skills depend on the creature's proficiencyBonus
|
||||||
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
dependencyGraph.addLink(prop._id, 'proficiencyBonus', 'skillProficiencyBonus');
|
||||||
|
|
||||||
|
// Skills can apply their value as a proficiency bonus to calculations based on tag
|
||||||
|
if (prop.targetByTags) {
|
||||||
|
getEffectTagTargets(prop, computation).forEach(targetId => {
|
||||||
|
const targetProp = computation.propsById[targetId];
|
||||||
|
// Always target a field on the target property, applying a skill to an attribute or
|
||||||
|
// other skill isn't supported
|
||||||
|
const key = prop.targetField || getDefaultCalculationField(targetProp);
|
||||||
|
const calcObj = get(targetProp, key);
|
||||||
|
if (calcObj && calcObj.calculation) {
|
||||||
|
dependencyGraph.addLink(`${targetProp._id}.${key}`, prop._id, 'proficiency');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkSlot(dependencyGraph, prop) {
|
function linkSlot(dependencyGraph, prop) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||||
|
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||||
|
|
||||||
export default function computeCalculation(computation, node) {
|
export default function computeCalculation(computation, node) {
|
||||||
const calcObj = node.data;
|
const calcObj = node.data;
|
||||||
@@ -54,6 +55,9 @@ function aggregateCalculationProficiencies(node, computation) {
|
|||||||
const calcObj = node.data;
|
const calcObj = node.data;
|
||||||
delete calcObj.proficiencies;
|
delete calcObj.proficiencies;
|
||||||
delete calcObj.proficiency;
|
delete calcObj.proficiency;
|
||||||
|
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
||||||
|
|
||||||
|
// Go through all the links and collect them on the calculation
|
||||||
computation.dependencyGraph.forEachLinkedNode(
|
computation.dependencyGraph.forEachLinkedNode(
|
||||||
node.id,
|
node.id,
|
||||||
(linkedNode, link) => {
|
(linkedNode, link) => {
|
||||||
@@ -61,40 +65,51 @@ function aggregateCalculationProficiencies(node, computation) {
|
|||||||
if (link.data !== 'proficiency') return;
|
if (link.data !== 'proficiency') return;
|
||||||
// That have data
|
// That have data
|
||||||
if (!linkedNode.data) return;
|
if (!linkedNode.data) return;
|
||||||
// Ignore inactive props
|
// Ignoring inactive props
|
||||||
if (linkedNode.data.inactive) return;
|
if (linkedNode.data.inactive) return;
|
||||||
|
// Compute the proficiency and value
|
||||||
|
let proficiency, value;
|
||||||
|
if (linkedNode.data.type === 'proficiency') {
|
||||||
|
proficiency = linkedNode.data.value || 0;
|
||||||
|
// Multiply the proficiency bonus by the actual proficiency
|
||||||
|
if (proficiency === 0.49) {
|
||||||
|
// Round down proficiency bonus in the special case
|
||||||
|
value = Math.floor(profBonus * 0.5);
|
||||||
|
} else {
|
||||||
|
value = Math.ceil(profBonus * proficiency);
|
||||||
|
}
|
||||||
|
} else if (linkedNode.data.type === 'skill') {
|
||||||
|
value = linkedNode.data.value || 0;
|
||||||
|
proficiency = linkedNode.data.proficiency || 0;
|
||||||
|
}
|
||||||
// Collate proficiencies
|
// Collate proficiencies
|
||||||
calcObj.proficiencies = calcObj.proficiencies || [];
|
calcObj.proficiencies = calcObj.proficiencies || [];
|
||||||
calcObj.proficiencies.push({
|
calcObj.proficiencies.push({
|
||||||
_id: linkedNode.data._id,
|
_id: linkedNode.data._id,
|
||||||
name: linkedNode.data.name,
|
name: linkedNode.data.name,
|
||||||
value: linkedNode.data.value,
|
type: linkedNode.data.type,
|
||||||
|
proficiency,
|
||||||
|
value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
true // enumerate only outbound links
|
true // enumerate only outbound links
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Apply the highest proficiency, marking all others as overridden
|
||||||
if (calcObj.proficiencies && typeof calcObj.value === 'number') {
|
if (calcObj.proficiencies && typeof calcObj.value === 'number') {
|
||||||
calcObj.proficiency = 0;
|
calcObj.proficiency = 0;
|
||||||
|
calcObj.proficiencyBonus = 0;
|
||||||
let currentProf;
|
let currentProf;
|
||||||
calcObj.proficiencies.forEach(prof => {
|
calcObj.proficiencies.forEach(prof => {
|
||||||
if (prof.value > calcObj.proficiency) {
|
if (prof.value > calcObj.proficiencyBonus) {
|
||||||
if (currentProf) currentProf.overridden = true;
|
if (currentProf) currentProf.overridden = true;
|
||||||
calcObj.proficiency = prof.value;
|
calcObj.proficiencyBonus = prof.value;
|
||||||
|
calcObj.proficiency = prof.proficiency;
|
||||||
|
currentProf = prof;
|
||||||
} else {
|
} else {
|
||||||
prof.overridden = true;
|
prof.overridden = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Get the character's proficiency bonus to apply
|
calcObj.value += calcObj.proficiencyBonus;
|
||||||
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
|
||||||
calcObj.proficiencyBonus = profBonus;
|
|
||||||
let totalBonus;
|
|
||||||
// Multiply the proficiency bonus by the actual proficiency
|
|
||||||
if (calcObj.proficiency === 0.49) {
|
|
||||||
// Round down proficiency bonus in the special case
|
|
||||||
totalBonus = Math.floor(profBonus * 0.5);
|
|
||||||
} else {
|
|
||||||
totalBonus = Math.ceil(profBonus * calcObj.proficiency);
|
|
||||||
}
|
|
||||||
calcObj.value += totalBonus;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ let ActionSchema = createPropertySchema({
|
|||||||
attackRoll: {
|
attackRoll: {
|
||||||
type: 'fieldToCompute',
|
type: 'fieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
defaultValue: 'strength.modifier + proficiencyBonus',
|
|
||||||
},
|
},
|
||||||
// Calculation of how many times this action can be used
|
// Calculation of how many times this action can be used
|
||||||
uses: {
|
uses: {
|
||||||
|
|||||||
@@ -59,6 +59,51 @@ let SkillSchema = createPropertySchema({
|
|||||||
type: 'inlineCalculationFieldToCompute',
|
type: 'inlineCalculationFieldToCompute',
|
||||||
optional: true,
|
optional: true,
|
||||||
},
|
},
|
||||||
|
// Skills can apply their value to other calculations as a proficiency
|
||||||
|
// True when applying skill to tagged props
|
||||||
|
targetByTags: {
|
||||||
|
type: Boolean,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
// Which tags the proficiency is applied to
|
||||||
|
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,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let ComputedOnlySkillSchema = createPropertySchema({
|
let ComputedOnlySkillSchema = createPropertySchema({
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
>
|
>
|
||||||
<div class="effect-icon">
|
<div class="effect-icon">
|
||||||
<proficiency-icon
|
<proficiency-icon
|
||||||
:value="model.value"
|
:value="model.proficiency"
|
||||||
class="prof-icon"
|
class="prof-icon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<span
|
<span
|
||||||
class="effect-value mr-2"
|
class="effect-value mr-2"
|
||||||
>
|
>
|
||||||
{{ proficiencyValue }}
|
{{ displayedValue }}
|
||||||
</span>
|
</span>
|
||||||
{{ displayedText }}
|
{{ displayedText }}
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
@@ -26,34 +26,25 @@
|
|||||||
|
|
||||||
<script lang="js">
|
<script lang="js">
|
||||||
import ProficiencyIcon from '/imports/client/ui/properties/shared/ProficiencyIcon.vue';
|
import ProficiencyIcon from '/imports/client/ui/properties/shared/ProficiencyIcon.vue';
|
||||||
|
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ProficiencyIcon,
|
ProficiencyIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
hideBreadcrumbs: Boolean,
|
|
||||||
model: {
|
model: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
proficiencyBonus: {
|
|
||||||
type: Number,
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
displayedText(){
|
displayedText(){
|
||||||
return this.model.name || 'Proficiency'
|
return this.model.name || (this.model.type == 'proficiency' ? 'Proficiency' : 'Skill')
|
||||||
|
},
|
||||||
|
displayedValue() {
|
||||||
|
return numberToSignedString(this.model.value);
|
||||||
},
|
},
|
||||||
proficiencyValue(){
|
|
||||||
if (!this.proficiencyBonus) return;
|
|
||||||
if (this.model.value === 0.49){
|
|
||||||
return Math.floor(0.5 * this.proficiencyBonus);
|
|
||||||
} else {
|
|
||||||
return Math.ceil(this.model.value * this.proficiencyBonus);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
click(e){
|
click(e){
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
/>
|
/>
|
||||||
<computed-field
|
<computed-field
|
||||||
v-else
|
v-else
|
||||||
label="To Hit"
|
label="Base attack roll bonus"
|
||||||
prefix="1d20 + "
|
hint="Must be set for the action to have an attack roll"
|
||||||
hint="The bonus to attack if this action has an attack roll"
|
|
||||||
:model="model.attackRoll"
|
:model="model.attackRoll"
|
||||||
:error-messages="errors.attackRoll"
|
:error-messages="errors.attackRoll"
|
||||||
@change="({path, value, ack}) =>
|
@change="({path, value, ack}) =>
|
||||||
|
|||||||
@@ -81,6 +81,24 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</form-section>
|
</form-section>
|
||||||
|
<form-section name="Apply skill">
|
||||||
|
<smart-switch
|
||||||
|
label="Apply skill to targeted tags"
|
||||||
|
:value="model.targetByTags"
|
||||||
|
:error-messages="errors.targetByTags"
|
||||||
|
@change="change('targetByTags', ...arguments)"
|
||||||
|
/>
|
||||||
|
<v-expand-transition>
|
||||||
|
<tag-targeting
|
||||||
|
v-if="model.targetByTags"
|
||||||
|
:model="model"
|
||||||
|
:errors="errors"
|
||||||
|
@change="e => $emit('change', e)"
|
||||||
|
@push="e => $emit('push', e)"
|
||||||
|
@pull="e => $emit('pull', e)"
|
||||||
|
/>
|
||||||
|
</v-expand-transition>
|
||||||
|
</form-section>
|
||||||
<slot />
|
<slot />
|
||||||
</form-sections>
|
</form-sections>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,11 +109,13 @@ import ProficiencySelect from '/imports/client/ui/properties/forms/shared/Profic
|
|||||||
import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue';
|
import FormSection from '/imports/client/ui/properties/forms/shared/FormSection.vue';
|
||||||
import createListOfProperties from '/imports/client/ui/properties/forms/shared/lists/createListOfProperties.js';
|
import createListOfProperties from '/imports/client/ui/properties/forms/shared/lists/createListOfProperties.js';
|
||||||
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
|
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
|
||||||
|
import TagTargeting from '/imports/client/ui/properties/forms/shared/TagTargeting.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ProficiencySelect,
|
ProficiencySelect,
|
||||||
FormSection,
|
FormSection,
|
||||||
|
TagTargeting,
|
||||||
},
|
},
|
||||||
mixins: [propertyFormMixin],
|
mixins: [propertyFormMixin],
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
v-if="showValue"
|
v-if="showValue"
|
||||||
#value
|
#value
|
||||||
>
|
>
|
||||||
{{ model.value }}
|
{{ displayedValue }}
|
||||||
</template>
|
</template>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<slot name="prepend" />
|
<slot name="prepend" />
|
||||||
@@ -37,7 +37,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showValue() {
|
showValue() {
|
||||||
const value = this.model.value;
|
let value = this.displayedValue;
|
||||||
if (
|
if (
|
||||||
this.hideValue ||
|
this.hideValue ||
|
||||||
(value === undefined || value === null) ||
|
(value === undefined || value === null) ||
|
||||||
@@ -45,6 +45,14 @@ export default {
|
|||||||
) return false;
|
) return false;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
displayedValue() {
|
||||||
|
let value = this.model.value;
|
||||||
|
// Use the base value instead if the calculation has it, because effects can modify the value
|
||||||
|
if (this.model.baseValue !== undefined) {
|
||||||
|
value = this.model.baseValue;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
errorList(){
|
errorList(){
|
||||||
if (this.model.parseError){
|
if (this.model.parseError){
|
||||||
return [this.model.parseError, ...this.model.errors];
|
return [this.model.parseError, ...this.model.errors];
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
style="max-width: 100%;"
|
style="max-width: 100%;"
|
||||||
>
|
>
|
||||||
<inline-effect
|
<inline-effect
|
||||||
v-if="typeof calculation.value === 'number'"
|
v-if="typeof calculation.value === 'number' && calculation.baseValue !== 0"
|
||||||
hide-breadcrumbs
|
hide-breadcrumbs
|
||||||
:model="{
|
:model="{
|
||||||
name: 'Base value',
|
name: 'Base value',
|
||||||
|
|||||||
Reference in New Issue
Block a user