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' });
|
||||
}
|
||||
|
||||
function linkSkill(dependencyGraph, prop) {
|
||||
function linkSkill(dependencyGraph, prop, computation) {
|
||||
// Depends on base value
|
||||
dependOnCalc({ dependencyGraph, prop, key: 'baseValue' });
|
||||
// Link dependents
|
||||
@@ -318,6 +318,20 @@ function linkSkill(dependencyGraph, prop) {
|
||||
}
|
||||
// Skills depend on the creature's proficiencyBonus
|
||||
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) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import evaluateCalculation from '../../utility/evaluateCalculation.js';
|
||||
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
|
||||
|
||||
export default function computeCalculation(computation, node) {
|
||||
const calcObj = node.data;
|
||||
@@ -54,6 +55,9 @@ function aggregateCalculationProficiencies(node, computation) {
|
||||
const calcObj = node.data;
|
||||
delete calcObj.proficiencies;
|
||||
delete calcObj.proficiency;
|
||||
let profBonus = computation.scope['proficiencyBonus']?.value || 0;
|
||||
|
||||
// Go through all the links and collect them on the calculation
|
||||
computation.dependencyGraph.forEachLinkedNode(
|
||||
node.id,
|
||||
(linkedNode, link) => {
|
||||
@@ -61,40 +65,51 @@ function aggregateCalculationProficiencies(node, computation) {
|
||||
if (link.data !== 'proficiency') return;
|
||||
// That have data
|
||||
if (!linkedNode.data) return;
|
||||
// Ignore inactive props
|
||||
// Ignoring inactive props
|
||||
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
|
||||
calcObj.proficiencies = calcObj.proficiencies || [];
|
||||
calcObj.proficiencies.push({
|
||||
_id: linkedNode.data._id,
|
||||
name: linkedNode.data.name,
|
||||
value: linkedNode.data.value,
|
||||
type: linkedNode.data.type,
|
||||
proficiency,
|
||||
value,
|
||||
});
|
||||
},
|
||||
true // enumerate only outbound links
|
||||
);
|
||||
|
||||
// Apply the highest proficiency, marking all others as overridden
|
||||
if (calcObj.proficiencies && typeof calcObj.value === 'number') {
|
||||
calcObj.proficiency = 0;
|
||||
calcObj.proficiencyBonus = 0;
|
||||
let currentProf;
|
||||
calcObj.proficiencies.forEach(prof => {
|
||||
if (prof.value > calcObj.proficiency) {
|
||||
if (prof.value > calcObj.proficiencyBonus) {
|
||||
if (currentProf) currentProf.overridden = true;
|
||||
calcObj.proficiency = prof.value;
|
||||
calcObj.proficiencyBonus = prof.value;
|
||||
calcObj.proficiency = prof.proficiency;
|
||||
currentProf = prof;
|
||||
} else {
|
||||
prof.overridden = true;
|
||||
}
|
||||
});
|
||||
// Get the character's proficiency bonus to apply
|
||||
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;
|
||||
calcObj.value += calcObj.proficiencyBonus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ let ActionSchema = createPropertySchema({
|
||||
attackRoll: {
|
||||
type: 'fieldToCompute',
|
||||
optional: true,
|
||||
defaultValue: 'strength.modifier + proficiencyBonus',
|
||||
},
|
||||
// Calculation of how many times this action can be used
|
||||
uses: {
|
||||
|
||||
@@ -59,6 +59,51 @@ let SkillSchema = createPropertySchema({
|
||||
type: 'inlineCalculationFieldToCompute',
|
||||
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({
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<div class="effect-icon">
|
||||
<proficiency-icon
|
||||
:value="model.value"
|
||||
:value="model.proficiency"
|
||||
class="prof-icon"
|
||||
/>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
<span
|
||||
class="effect-value mr-2"
|
||||
>
|
||||
{{ proficiencyValue }}
|
||||
{{ displayedValue }}
|
||||
</span>
|
||||
{{ displayedText }}
|
||||
</v-list-item-title>
|
||||
@@ -26,34 +26,25 @@
|
||||
|
||||
<script lang="js">
|
||||
import ProficiencyIcon from '/imports/client/ui/properties/shared/ProficiencyIcon.vue';
|
||||
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProficiencyIcon,
|
||||
},
|
||||
props: {
|
||||
hideBreadcrumbs: Boolean,
|
||||
model: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
proficiencyBonus: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
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: {
|
||||
click(e){
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
/>
|
||||
<computed-field
|
||||
v-else
|
||||
label="To Hit"
|
||||
prefix="1d20 + "
|
||||
hint="The bonus to attack if this action has an attack roll"
|
||||
label="Base attack roll bonus"
|
||||
hint="Must be set for the action to have an attack roll"
|
||||
:model="model.attackRoll"
|
||||
:error-messages="errors.attackRoll"
|
||||
@change="({path, value, ack}) =>
|
||||
|
||||
@@ -81,6 +81,24 @@
|
||||
</v-col>
|
||||
</v-row>
|
||||
</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 />
|
||||
</form-sections>
|
||||
</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 createListOfProperties from '/imports/client/ui/properties/forms/shared/lists/createListOfProperties.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 {
|
||||
components: {
|
||||
ProficiencySelect,
|
||||
FormSection,
|
||||
TagTargeting,
|
||||
},
|
||||
mixins: [propertyFormMixin],
|
||||
data() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
v-if="showValue"
|
||||
#value
|
||||
>
|
||||
{{ model.value }}
|
||||
{{ displayedValue }}
|
||||
</template>
|
||||
<template #prepend>
|
||||
<slot name="prepend" />
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
showValue() {
|
||||
const value = this.model.value;
|
||||
let value = this.displayedValue;
|
||||
if (
|
||||
this.hideValue ||
|
||||
(value === undefined || value === null) ||
|
||||
@@ -45,6 +45,14 @@ export default {
|
||||
) return false;
|
||||
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(){
|
||||
if (this.model.parseError){
|
||||
return [this.model.parseError, ...this.model.errors];
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
style="max-width: 100%;"
|
||||
>
|
||||
<inline-effect
|
||||
v-if="typeof calculation.value === 'number'"
|
||||
v-if="typeof calculation.value === 'number' && calculation.baseValue !== 0"
|
||||
hide-breadcrumbs
|
||||
:model="{
|
||||
name: 'Base value',
|
||||
|
||||
Reference in New Issue
Block a user