Added custom sheet events

Made rest buttons optional
This commit is contained in:
Stefan Zermatten
2022-11-08 23:01:09 +02:00
parent 60b21c1901
commit 1ec29365cb
17 changed files with 481 additions and 275 deletions

View File

@@ -18,6 +18,11 @@ let CreatureSettingsSchema = new SimpleSchema({
type: Boolean,
optional: true,
},
//hide rest buttons
hideRestButtons: {
type: Boolean,
optional: true,
},
// Swap around the modifier and stat
swapStatAndModifier: {
type: Boolean,

View File

@@ -49,7 +49,7 @@ const restCreature = new ValidatedMethod({
applyTriggers(afterTriggers, null, actionContext);
// Insert log
actionContext.writeLog();
actionContext.writeLog();
},
});
@@ -57,88 +57,123 @@ function doRestWork(restType, actionContext) {
const creatureId = actionContext.creature._id;
// Long rests reset short rest properties as well
let resetFilter;
if (restType === 'shortRest'){
if (restType === 'shortRest') {
resetFilter = 'shortRest'
} else {
resetFilter = {$in: ['shortRest', 'longRest']}
resetFilter = { $in: ['shortRest', 'longRest'] }
}
resetProperties(creatureId, resetFilter, actionContext);
// Reset half hit dice on a long rest, starting with the highest dice
if (restType === 'longRest') {
resetHitDice(creatureId, actionContext);
}
}
export function resetProperties(creatureId, resetFilter, actionContext) {
// Only apply to active properties
let filter = {
const filter = {
'ancestors.id': creatureId,
reset: resetFilter,
removed: { $ne: true },
inactive: { $ne: true },
};
// update all attribute's damage
filter.type = 'attribute';
CreatureProperties.update(filter, {
const attributeFilter = {
...filter,
type: 'attribute',
damage: { $ne: 0 },
}
CreatureProperties.find(attributeFilter, {
fields: { name: 1, damage: 1 }
}).forEach(prop => {
actionContext.addLog({
name: prop.name,
value: prop.damage >= 0 ? `Restored ${prop.damage}` : `Removed ${-prop.damage}`
});
});
CreatureProperties.update(attributeFilter, {
$set: {
damage: 0,
dirty: true,
}
}, {
selector: {type: 'attribute'},
selector: { type: 'attribute' },
multi: true,
});
// Update all action-like properties' usesUsed
filter.type = {$in: [
'action',
'attack',
'spell'
]};
CreatureProperties.update(filter, {
const actionFilter = {
...filter,
type: {
$in: ['action', 'spell']
},
usesUsed: { $ne: 0 },
};
CreatureProperties.find(actionFilter, {
fields: { name: 1, usesUsed: 1 }
}).forEach(prop => {
actionContext.addLog({
name: prop.name,
value: prop.usesUsed >= 0 ? `Restored ${prop.usesUsed} uses` : `Removed ${-prop.usesUsed} uses`
});
});
CreatureProperties.update(actionFilter, {
$set: {
usesUsed: 0,
dirty: true,
}
}, {
selector: {type: 'action'},
selector: { type: 'action' },
multi: true,
});
// Reset half hit dice on a long rest, starting with the highest dice
if (restType === 'longRest'){
let hitDice = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'attribute',
attributeType: 'hitDice',
removed: {$ne: true},
inactive: {$ne: true},
}, {
fields: {
hitDiceSize: 1,
damage: 1,
total: 1,
}
function resetHitDice(creatureId, actionContext) {
let hitDice = CreatureProperties.find({
'ancestors.id': creatureId,
type: 'attribute',
attributeType: 'hitDice',
removed: { $ne: true },
inactive: { $ne: true },
}, {
fields: {
hitDiceSize: 1,
damage: 1,
total: 1,
}
}).fetch();
// Use a collator to do sorting in natural order
let collator = new Intl.Collator('en', {
numeric: true, sensitivity: 'base'
});
// Get the hit dice in decending order of hitDiceSize
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
hitDice.sort(compare);
// Get the total number of hit dice that can be recovered this rest
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0);
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
let recoverableHd = Math.max(Math.floor(totalHd * resetMultiplier), 1);
// recover each hit dice in turn until the recoverable amount is used up
let amountToRecover, resultingDamage;
hitDice.forEach(hd => {
if (!recoverableHd) return;
amountToRecover = Math.min(recoverableHd, hd.damage || 0);
if (!amountToRecover) return;
recoverableHd -= amountToRecover;
resultingDamage = hd.damage - amountToRecover;
actionContext.addLog({
name: hd.name,
value: amountToRecover >= 0 ? `Restored ${amountToRecover} hit dice` : `Removed ${-amountToRecover} hit dice`
});
CreatureProperties.update(hd._id, {
$set: {
damage: resultingDamage,
dirty: true,
}
}).fetch();
// Use a collator to do sorting in natural order
let collator = new Intl.Collator('en', {
numeric: true, sensitivity: 'base'
}, {
selector: { type: 'attribute' },
});
// Get the hit dice in decending order of hitDiceSize
let compare = (a, b) => collator.compare(b.hitDiceSize, a.hitDiceSize)
hitDice.sort(compare);
// Get the total number of hit dice that can be recovered this rest
let totalHd = hitDice.reduce((sum, hd) => sum + (hd.total || 0), 0);
let resetMultiplier = actionContext.creature.settings.hitDiceResetMultiplier || 0.5;
let recoverableHd = Math.max(Math.floor(totalHd*resetMultiplier), 1);
// recover each hit dice in turn until the recoverable amount is used up
let amountToRecover, resultingDamage;
hitDice.forEach(hd => {
if (!recoverableHd) return;
amountToRecover = Math.min(recoverableHd, hd.damage || 0);
if (!amountToRecover) return;
recoverableHd -= amountToRecover;
resultingDamage = hd.damage - amountToRecover;
CreatureProperties.update(hd._id, {
$set: {
damage: resultingDamage,
dirty: true,
}
}, {
selector: {type: 'attribute'},
});
});
}
});
}
export default restCreature;

View File

@@ -7,6 +7,7 @@ import { adjustQuantityWork } from '/imports/api/creature/creatureProperties/met
import { damagePropertyWork } from '/imports/api/creature/creatureProperties/methods/damageProperty.js';
import numberToSignedString from '/imports/ui/utility/numberToSignedString.js';
import { applyNodeTriggers } from '/imports/api/engine/actions/applyTriggers.js';
import { resetProperties } from '/imports/api/creature/creatures/methods/restCreature.js';
export default function applyAction(node, actionContext) {
applyNodeTriggers(node, 'before', actionContext);
@@ -16,7 +17,7 @@ export default function applyAction(node, actionContext) {
// Log the name and summary
let content = { name: prop.name };
if (prop.summary?.text){
if (prop.summary?.text) {
recalculateInlineCalculations(prop.summary, actionContext);
content.value = prop.summary.value;
}
@@ -29,24 +30,27 @@ export default function applyAction(node, actionContext) {
const attack = prop.attackRoll || prop.attackRollBonus;
// Attack if there is an attack roll
if (attack && attack.calculation){
if (targets.length){
if (attack && attack.calculation) {
if (targets.length) {
targets.forEach(target => {
applyAttackToTarget({attack, target, actionContext});
applyAttackToTarget({ attack, target, actionContext });
// Apply the children, but only to the current target
actionContext.targets = [target];
applyChildren(node, actionContext);
});
} else {
applyAttackWithoutTarget({attack, actionContext});
applyAttackWithoutTarget({ attack, actionContext });
applyChildren(node, actionContext);
}
} else {
applyChildren(node, actionContext);
}
if (prop.actionType === 'event' && prop.variableName) {
resetProperties(actionContext.creature._id, prop.variableName, actionContext);
}
}
function applyAttackWithoutTarget({attack, actionContext}){
function applyAttackWithoutTarget({ attack, actionContext }) {
delete actionContext.scope['$attackHit'];
delete actionContext.scope['$attackMiss'];
delete actionContext.scope['$criticalHit'];
@@ -62,16 +66,16 @@ function applyAttackWithoutTarget({attack, actionContext}){
criticalMiss,
} = rollAttack(attack, scope);
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
if (scope['$attackAdvantage'] === 1){
if (scope['$attackAdvantage'] === 1) {
name += ' (Advantage)';
} else if(scope['$attackAdvantage'] === -1){
} else if (scope['$attackAdvantage'] === -1) {
name += ' (Disadvantage)';
}
if (!criticalMiss){
scope['$attackHit'] = {value: true}
if (!criticalMiss) {
scope['$attackHit'] = { value: true }
}
if (!criticalHit){
scope['$attackMiss'] = {value: true};
if (!criticalHit) {
scope['$attackMiss'] = { value: true };
}
actionContext.addLog({
@@ -81,7 +85,7 @@ function applyAttackWithoutTarget({attack, actionContext}){
});
}
function applyAttackToTarget({attack, target, actionContext}){
function applyAttackToTarget({ attack, target, actionContext }) {
const scope = actionContext.scope;
delete scope['$attackHit'];
delete scope['$attackMiss'];
@@ -99,15 +103,15 @@ function applyAttackToTarget({attack, target, actionContext}){
criticalMiss,
} = rollAttack(attack, scope);
if (target.variables.armor){
if (target.variables.armor) {
const armor = target.variables.armor.value;
let name = criticalHit ? 'Critical Hit!' :
criticalMiss ? 'Critical Miss!' :
result > armor ? 'Hit!' : 'Miss!';
if (scope['$attackAdvantage'] === 1){
result > armor ? 'Hit!' : 'Miss!';
if (scope['$attackAdvantage'] === 1) {
name += ' (Advantage)';
} else if(scope['$attackAdvantage'] === -1){
} else if (scope['$attackAdvantage'] === -1) {
name += ' (Disadvantage)';
}
@@ -116,15 +120,15 @@ function applyAttackToTarget({attack, target, actionContext}){
value: `${resultPrefix}\n**${result}**`,
inline: true,
});
if (criticalMiss || result < armor){
scope['$attackMiss'] = {value: true};
if (criticalMiss || result < armor) {
scope['$attackMiss'] = { value: true };
} else {
scope['$attackHit'] = {value: true};
scope['$attackHit'] = { value: true };
}
} else {
actionContext.addLog({
name: 'Error',
value:'Target has no `armor`',
value: 'Target has no `armor`',
});
actionContext.addLog({
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
@@ -134,10 +138,10 @@ function applyAttackToTarget({attack, target, actionContext}){
}
}
function rollAttack(attack, scope){
function rollAttack(attack, scope) {
const rollModifierText = numberToSignedString(attack.value, true);
let value, resultPrefix;
if (scope['$attackAdvantage'] === 1){
if (scope['$attackAdvantage'] === 1) {
const [a, b] = rollDice(2, 20);
if (a >= b) {
value = a;
@@ -146,7 +150,7 @@ function rollAttack(attack, scope){
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] ${rollModifierText}`;
}
} else if (scope['$attackAdvantage'] === -1){
} else if (scope['$attackAdvantage'] === -1) {
const [a, b] = rollDice(2, 20);
if (a <= b) {
value = a;
@@ -159,25 +163,25 @@ function rollAttack(attack, scope){
value = rollDice(1, 20)[0];
resultPrefix = `1d20 [${value}] ${rollModifierText}`
}
scope['$attackRoll'] = {value};
scope['$attackRoll'] = { value };
const result = value + attack.value;
const {criticalHit, criticalMiss} = applyCrits(value, scope);
return {resultPrefix, result, value, criticalHit, criticalMiss};
const { criticalHit, criticalMiss } = applyCrits(value, scope);
return { resultPrefix, result, value, criticalHit, criticalMiss };
}
function applyCrits(value, scope){
function applyCrits(value, scope) {
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
let criticalHit = value >= criticalHitTarget;
let criticalMiss;
if (criticalHit){
scope['$criticalHit'] = {value: true};
if (criticalHit) {
scope['$criticalHit'] = { value: true };
} else {
criticalMiss = value === 1;
if (criticalMiss){
scope['$criticalMiss'] = {value: true};
if (criticalMiss) {
scope['$criticalMiss'] = { value: true };
}
}
return {criticalHit, criticalMiss};
return { criticalHit, criticalMiss };
}
function applyChildren(node, actionContext) {
@@ -185,9 +189,9 @@ function applyChildren(node, actionContext) {
node.children.forEach(child => applyProperty(child, actionContext));
}
function spendResources(prop, actionContext){
function spendResources(prop, actionContext) {
// Check Uses
if (prop.usesLeft <= 0){
if (prop.usesLeft <= 0) {
if (!prop.silent) actionContext.addLog({
name: 'Error',
value: `${prop.name || 'action'} does not have enough uses left`,
@@ -195,7 +199,7 @@ function spendResources(prop, actionContext){
return true;
}
// Resources
if (prop.insufficientResources){
if (prop.insufficientResources) {
if (!prop.silent) actionContext.addLog({
name: 'Error',
value: 'This creature doesn\'t have sufficient resources to perform this action',
@@ -209,14 +213,14 @@ function spendResources(prop, actionContext){
try {
prop.resources.itemsConsumed.forEach(itemConsumed => {
recalculateCalculation(itemConsumed.quantity, actionContext);
if (!itemConsumed.itemId){
if (!itemConsumed.itemId) {
throw 'No ammo was selected for this prop';
}
let item = CreatureProperties.findOne(itemConsumed.itemId);
if (!item || item.ancestors[0].id !== prop.ancestors[0].id){
if (!item || item.ancestors[0].id !== prop.ancestors[0].id) {
throw 'The prop\'s ammo was not found on the creature';
}
if (!item.equipped){
if (!item.equipped) {
throw 'The selected ammo is not equipped';
}
if (
@@ -229,16 +233,16 @@ function spendResources(prop, actionContext){
value: itemConsumed.quantity.value,
});
let logName = item.name;
if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1){
if (itemConsumed.quantity.value > 1 || itemConsumed.quantity.value < -1) {
logName = item.plural || logName;
}
if (itemConsumed.quantity.value > 0){
if (itemConsumed.quantity.value > 0) {
spendLog.push(logName + ': ' + itemConsumed.quantity.value);
} else if (itemConsumed.quantity.value < 0){
} else if (itemConsumed.quantity.value < 0) {
gainLog.push(logName + ': ' + -itemConsumed.quantity.value);
}
});
} catch (e){
} catch (e) {
actionContext.addLog({
name: 'Error',
value: e,
@@ -251,9 +255,9 @@ function spendResources(prop, actionContext){
itemQuantityAdjustments.forEach(adjustQuantityWork);
// Use uses
if (prop.usesLeft){
if (prop.usesLeft) {
CreatureProperties.update(prop._id, {
$inc: {usesUsed: 1}
$inc: { usesUsed: 1 }
}, {
selector: prop
});
@@ -270,7 +274,7 @@ function spendResources(prop, actionContext){
if (!attConsumed.quantity?.value) return;
let stat = actionContext.scope[attConsumed.variableName];
if (!stat){
if (!stat) {
spendLog.push(stat.name + ': ' + ' not found');
return;
}
@@ -280,9 +284,9 @@ function spendResources(prop, actionContext){
value: attConsumed.quantity.value,
actionContext,
});
if (attConsumed.quantity.value > 0){
if (attConsumed.quantity.value > 0) {
spendLog.push(stat.name + ': ' + attConsumed.quantity.value);
} else if (attConsumed.quantity.value < 0){
} else if (attConsumed.quantity.value < 0) {
gainLog.push(stat.name + ': ' + -attConsumed.quantity.value);
}
});

View File

@@ -2,6 +2,7 @@ import SimpleSchema from 'simpl-schema';
import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js';
import { storedIconsSchema } from '/imports/api/icons/Icons.js';
import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
/*
* Actions are things a character can do
@@ -24,9 +25,17 @@ let ActionSchema = createPropertySchema({
// long actions take longer than 1 round to cast
actionType: {
type: String,
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long'],
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long', 'event'],
defaultValue: 'action',
},
// If the action type is an event, what is the variable name of that event?
variableName: {
type: String,
optional: true,
regEx: VARIABLE_NAME_REGEX,
min: 2,
max: STORAGE_LIMITS.variableName,
},
// Who is the action directed at
target: {
type: String,
@@ -56,8 +65,10 @@ let ActionSchema = createPropertySchema({
// How this action's uses are reset automatically
reset: {
type: String,
allowedValues: ['longRest', 'shortRest'],
optional: true,
regEx: VARIABLE_NAME_REGEX,
min: 2,
max: STORAGE_LIMITS.variableName,
},
// Resources
resources: {
@@ -74,7 +85,7 @@ let ActionSchema = createPropertySchema({
'resources.itemsConsumed.$._id': {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
autoValue() {
if (!this.isSet) return Random.id();
}
},
@@ -101,7 +112,7 @@ let ActionSchema = createPropertySchema({
'resources.attributesConsumed.$._id': {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
autoValue() {
if (!this.isSet) return Random.id();
}
},
@@ -218,4 +229,4 @@ const ComputedActionSchema = new SimpleSchema()
.extend(ActionSchema)
.extend(ComputedOnlyActionSchema);
export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema};
export { ActionSchema, ComputedOnlyActionSchema, ComputedActionSchema };

View File

@@ -129,7 +129,9 @@ let AttributeSchema = createPropertySchema({
reset: {
type: String,
optional: true,
allowedValues: ['shortRest', 'longRest'],
regEx: VARIABLE_NAME_REGEX,
min: 2,
max: STORAGE_LIMITS.variableName,
},
});

View File

@@ -0,0 +1,46 @@
<template>
<smart-select
label="Reset"
clearable
style="flex-basis: 300px;"
:hint="hint"
:items="resetOptions"
:value="value"
:error-messages="errorMessages"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', value, ack)"
/>
</template>
<script lang="js">
import createListOfProperties from '/imports/ui/properties/forms/shared/lists/createListOfProperties.js';
export default {
props: {
value: [String, Number, Date, Array, Object, Boolean],
errorMessages: [String, Array],
hint: {
type: String,
default: undefined,
}
},
meteor: {
resetOptions() {
const eventActions = createListOfProperties({
type: 'action',
actionType: 'event',
}, true);
const defaultEvents = [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
];
return [...defaultEvents, ...eventActions];
},
},
}
</script>

View File

@@ -39,6 +39,11 @@
:input-value="model.settings.hideUnusedStats"
@change="value => $emit('change', {path: ['settings','hideUnusedStats'], value: !!value})"
/>
<v-switch
label="Hide rest buttons"
:input-value="model.settings.hideRestButtons"
@change="value => $emit('change', {path: ['settings','hideRestButtons'], value: !!value})"
/>
<v-switch
label="Show spells tab"
:input-value="!model.settings.hideSpellsTab"

View File

@@ -3,19 +3,30 @@
<health-bar-card-container :creature-id="creatureId" />
<column-layout>
<div class="character-buttons">
<div
v-if="!creature.settings.hideRestButtons || (events && events.length)"
class="character-buttons"
>
<v-card>
<v-card-text class="layout column align-center">
<rest-button
v-if="!creature.settings.hideRestButtons"
:creature-id="creatureId"
type="shortRest"
class="ma-1"
/>
<rest-button
v-if="!creature.settings.hideRestButtons"
:creature-id="creatureId"
type="longRest"
class="ma-1"
/>
<event-button
v-for="event in events"
:key="event._id"
:model="event"
class="ma-1"
/>
</v-card-text>
</v-card>
</div>
@@ -352,7 +363,9 @@ import RestButton from '/imports/ui/creature/RestButton.vue';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import ToggleCard from '/imports/ui/properties/components/toggles/ToggleCard.vue';
import doCastSpell from '/imports/api/engine/actions/doCastSpell.js';
import EventButton from '/imports/ui/properties/components/actions/EventButton.vue';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
import { uniqBy } from 'lodash';
const getProperties = function (creature, filter, options = {
sort: { order: 1 }
@@ -401,6 +414,7 @@ export default {
SpellSlotListTile,
ActionCard,
ToggleCard,
EventButton,
},
props: {
creatureId: {
@@ -473,8 +487,12 @@ export default {
languages() {
return getSkillOfType(this.creature, 'language');
},
events() {
const events = getProperties(this.creature, { type: 'action', actionType: 'event' });
return uniqBy(events.fetch(), e => e.variableName);
},
actions() {
return getProperties(this.creature, { type: 'action' });
return getProperties(this.creature, { type: 'action', actionType: { $ne: 'event' } });
},
appliedBuffs() {
return getProperties(this.creature, { type: 'buff' });

View File

@@ -0,0 +1,80 @@
<template lang="html">
<v-btn
:loading="doActionLoading"
:disabled="context.editPermission === false"
outlined
class="event-button"
style="min-width: 160px; max-width: 100%;"
:color="model.color"
@click="doAction"
>
<property-icon
style="margin-left: -4px; margin-right: 8px;"
:model="model"
/>
<div
class="text-truncate"
>
{{ model.name }}
</div>
</v-btn>
</template>
<script lang="js">
import doAction from '/imports/api/engine/actions/doAction.js';
import PropertyIcon from '/imports/ui/properties/shared/PropertyIcon.vue';
import { snackbar } from '/imports/ui/components/snackbars/SnackbarQueue.js';
export default {
components: {
PropertyIcon,
},
inject: {
context: { default: {} }
},
props: {
model: {
type: Object,
required: true,
},
},
data(){return {
activated: undefined,
doActionLoading: false,
hovering: false,
}},
methods: {
click(e) {
this.$emit('click', e);
},
doAction({ advantage }) {
this.doActionLoading = true;
this.shwing();
doAction.call({
actionId: this.model._id,
scope: {
$attackAdvantage: advantage,
}
}, error => {
this.doActionLoading = false;
if (error) {
console.error(error);
snackbar({ text: error.reason });
}
});
},
shwing() {
this.activated = true;
setTimeout(() => {
this.activated = undefined;
}, 150);
}
}
}
</script>
<style lang="css">
.event-button .v-btn__content {
max-width: 100%;
}
</style>

View File

@@ -1,7 +1,9 @@
<template lang="html">
<v-card
:hover="hasClickListener"
:class="hover ? 'elevation-8': ''"
@click="click"
@mouseover="hover = true"
@mouseleave="hover = false"
>
<div class="layout align-center">
<div
@@ -18,19 +20,29 @@
{{ model.name }}
</v-card-title>
</div>
<card-highlight :active="hover" />
</v-card>
</template>
<script lang="js">
import flipToggle from '/imports/api/creature/creatureProperties/methods/flipToggle.js';
import CardHighlight from '/imports/ui/components/CardHighlight.vue';
export default {
components: {
CardHighlight,
},
props: {
model: {
type: Object,
required: true,
},
},
data() {
return {
hover: false,
}
},
computed: {
hasClickListener(){
return this.$listeners && !!this.$listeners.click

View File

@@ -41,6 +41,17 @@
</v-col>
</v-row>
<v-slide-x-transition mode="out-in">
<text-field
v-if="model.actionType === 'event'"
label="Event variable name"
:value="model.variableName"
hint="Variable name of the event that this action represents"
:error-messages="errors.variableName"
@change="change('variableName', ...arguments)"
/>
</v-slide-x-transition>
<v-slide-x-transition mode="out-in">
<v-switch
v-if="!isAttack"
@@ -154,15 +165,10 @@
/>
</v-col>
</v-row>
<smart-select
label="Reset"
clearable
<reset-selector
hint="When number of uses used should be reset to zero"
style="flex-basis: 300px;"
:items="resetOptions"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="change('reset', ...arguments)"
/>
</form-section>
@@ -171,77 +177,74 @@
</template>
<script lang="js">
import ResourcesForm from '/imports/ui/properties/forms/ResourcesForm.vue';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import IconColorMenu from '/imports/ui/properties/forms/shared/IconColorMenu.vue';
import ResourcesForm from '/imports/ui/properties/forms/ResourcesForm.vue';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import IconColorMenu from '/imports/ui/properties/forms/shared/IconColorMenu.vue';
import ResetSelector from '/imports/ui/components/ResetSelector.vue';
export default {
components: {
ResourcesForm,
IconColorMenu,
},
mixins: [propertyFormMixin],
data(){
let data = {
actionTypes: [
{
text: 'Action',
value: 'action',
}, {
text: 'Bonus action',
value: 'bonus',
}, {
text: 'Attack action',
value: 'attack',
help: 'Attack actions replace a single attack when you choose to use your Action to attack',
}, {
text: 'Reaction',
value: 'reaction',
}, {
text: 'Free action',
value: 'free',
help: 'You can take one free action on your turn without using an action or bonus action'
}, {
text: 'Long action',
value: 'long',
help: 'Long actions take longer than one turn to complete'
},
],
targetOptions: [
{
text: 'Self',
value: 'self',
}, {
text: 'Single target',
value: 'singleTarget',
}, {
text: 'Multiple targets',
value: 'multipleTargets',
},
],
resetOptions: [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
],
attackSwitch: false,
};
data.actionTypeHints = {};
data.actionTypes.forEach(type => {
data.actionTypeHints[type.value] = type.help;
});
return data;
},
computed: {
isAttack(){
return this.attackSwitch || !!this.model.attackRoll?.calculation
}
export default {
components: {
ResourcesForm,
IconColorMenu,
ResetSelector,
},
mixins: [propertyFormMixin],
data(){
let data = {
actionTypes: [
{
text: 'Action',
value: 'action',
}, {
text: 'Bonus action',
value: 'bonus',
}, {
text: 'Attack action',
value: 'attack',
help: 'Attack actions replace a single attack when you choose to use your Action to attack',
}, {
text: 'Reaction',
value: 'reaction',
}, {
text: 'Free action',
value: 'free',
help: 'You can take one free action on your turn without using an action or bonus action'
}, {
text: 'Long action',
value: 'long',
help: 'Long actions take longer than one turn to complete'
}, {
text: 'Event',
value: 'event',
help: 'Events are actions that happen to the character like rests or dawn'
},
],
targetOptions: [
{
text: 'Self',
value: 'self',
}, {
text: 'Single target',
value: 'singleTarget',
}, {
text: 'Multiple targets',
value: 'multipleTargets',
},
],
attackSwitch: false,
};
data.actionTypeHints = {};
data.actionTypes.forEach(type => {
data.actionTypeHints[type.value] = type.help;
});
return data;
},
computed: {
isAttack(){
return this.attackSwitch || !!this.model.attackRoll?.calculation
}
};
},
};
</script>
<style lang="css" scoped>

View File

@@ -250,16 +250,11 @@
</div>
</div>
<div class="layout wrap">
<smart-select
<reset-selector
v-if="model.attributeType !== 'hitDice'"
label="Reset"
clearable
style="flex-basis: 300px;"
hint="When damage should be reset to zero"
:items="resetOptions"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="change('reset', ...arguments)"
/>
</div>
@@ -273,12 +268,14 @@ import FormSection from '/imports/ui/properties/forms/shared/FormSection.vue';
import FormSections from '/imports/ui/properties/forms/shared/FormSections.vue';
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import ColorPicker from '/imports/ui/components/ColorPicker.vue';
import ResetSelector from '/imports/ui/components/ResetSelector.vue';
export default {
components: {
FormSection,
FormSections,
ColorPicker,
ResetSelector,
},
mixins: [propertyFormMixin],
inject: {

View File

@@ -278,15 +278,10 @@
/>
</v-col>
</v-row>
<smart-select
label="Reset"
clearable
<reset-selector
hint="When number of uses used should be reset to zero"
style="flex-basis: 300px;"
:items="resetOptions"
:value="model.reset"
:error-messages="errors.reset"
:menu-props="{auto: true, lazy: true}"
@change="change('reset', ...arguments)"
/>
</form-section>
@@ -318,6 +313,7 @@ import FormSection, { FormSections } from '/imports/ui/properties/forms/shared/F
import propertyFormMixin from '/imports/ui/properties/forms/shared/propertyFormMixin.js';
import IconColorMenu from '/imports/ui/properties/forms/shared/IconColorMenu.vue';
import ResourcesForm from '/imports/ui/properties/forms/ResourcesForm.vue';
import ResetSelector from '/imports/ui/components/ResetSelector.vue';
export default {
components: {
@@ -325,6 +321,7 @@ export default {
FormSection,
IconColorMenu,
ResourcesForm,
ResetSelector,
},
mixins: [propertyFormMixin],
data() {
@@ -401,15 +398,6 @@ export default {
value: 'multipleTargets',
},
],
resetOptions: [
{
text: 'Short rest',
value: 'shortRest',
}, {
text: 'Long rest',
value: 'longRest',
}
],
attackSwitch: false,
};
},

View File

@@ -1,12 +1,12 @@
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
export default function createListOfProperties(filter = {}){
filter.removed = {$ne: true};
export default function createListOfProperties(filter = {}, getNamesWithValues) {
filter.removed = { $ne: true };
let propertyList = [];
let variableNames = new Set();
function addUniquePropertys(property){
if (property.variableName && !variableNames.has(property.variableName)){
function addUniquePropertys(property) {
if (property.variableName && !variableNames.has(property.variableName)) {
variableNames.add(property.variableName);
propertyList.push({
text: property.name || property.variableName,
@@ -15,8 +15,9 @@ export default function createListOfProperties(filter = {}){
});
}
}
let options = {sort: {order: 1, variableName: 1}}
let options = { sort: { order: 1, variableName: 1 } }
CreatureProperties.find(filter, options).forEach(addUniquePropertys);
LibraryNodes.find(filter, options).forEach(addUniquePropertys);
if (getNamesWithValues) return propertyList;
return Array.from(variableNames);
}

View File

@@ -1,32 +1,32 @@
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');
const ConstantForm = () => import('/imports/ui/properties/forms/ConstantForm.vue');
const ContainerForm = () => import('/imports/ui/properties/forms/ContainerForm.vue');
const DamageForm = () => import('/imports/ui/properties/forms/DamageForm.vue');
const DamageMultiplierForm = () => import('/imports/ui/properties/forms/DamageMultiplierForm.vue');
const EffectForm = () => import('/imports/ui/properties/forms/EffectForm.vue');
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');
const SavingThrowForm = () => import('/imports/ui/properties/forms/SavingThrowForm.vue');
const SkillForm = () => import('/imports/ui/properties/forms/SkillForm.vue');
const SlotForm = () => import('/imports/ui/properties/forms/SlotForm.vue');
const SlotFillerForm = () => import('/imports/ui/properties/forms/SlotFillerForm.vue');
const SpellListForm = () => import('/imports/ui/properties/forms/SpellListForm.vue');
const SpellForm = () => import('/imports/ui/properties/forms/SpellForm.vue');
const ToggleForm = () => import('/imports/ui/properties/forms/ToggleForm.vue');
const TriggerForm = () => import('/imports/ui/properties/forms/TriggerForm.vue');
import ActionForm from '/imports/ui/properties/forms/ActionForm.vue';
import AdjustmentForm from '/imports/ui/properties/forms/AdjustmentForm.vue';
import AttributeForm from '/imports/ui/properties/forms/AttributeForm.vue';
import BuffForm from '/imports/ui/properties/forms/BuffForm.vue';
import BuffRemoverForm from '/imports/ui/properties/forms/BuffRemoverForm.vue';
import BranchForm from '/imports/ui/properties/forms/BranchForm.vue';
import ClassForm from '/imports/ui/properties/forms/ClassForm.vue';
import ClassLevelForm from '/imports/ui/properties/forms/ClassLevelForm.vue';
import ConstantForm from '/imports/ui/properties/forms/ConstantForm.vue';
import ContainerForm from '/imports/ui/properties/forms/ContainerForm.vue';
import DamageForm from '/imports/ui/properties/forms/DamageForm.vue';
import DamageMultiplierForm from '/imports/ui/properties/forms/DamageMultiplierForm.vue';
import EffectForm from '/imports/ui/properties/forms/EffectForm.vue';
import FeatureForm from '/imports/ui/properties/forms/FeatureForm.vue';
import FolderForm from '/imports/ui/properties/forms/FolderForm.vue';
import ItemForm from '/imports/ui/properties/forms/ItemForm.vue';
import NoteForm from '/imports/ui/properties/forms/NoteForm.vue';
import PointBuyForm from '/imports/ui/properties/forms/PointBuyForm.vue';
import ProficiencyForm from '/imports/ui/properties/forms/ProficiencyForm.vue';
import ReferenceForm from '/imports/ui/properties/forms/ReferenceForm.vue';
import RollForm from '/imports/ui/properties/forms/RollForm.vue';
import SavingThrowForm from '/imports/ui/properties/forms/SavingThrowForm.vue';
import SkillForm from '/imports/ui/properties/forms/SkillForm.vue';
import SlotForm from '/imports/ui/properties/forms/SlotForm.vue';
import SlotFillerForm from '/imports/ui/properties/forms/SlotFillerForm.vue';
import SpellListForm from '/imports/ui/properties/forms/SpellListForm.vue';
import SpellForm from '/imports/ui/properties/forms/SpellForm.vue';
import ToggleForm from '/imports/ui/properties/forms/ToggleForm.vue';
import TriggerForm from '/imports/ui/properties/forms/TriggerForm.vue';
export default {
action: ActionForm,

View File

@@ -1,32 +1,32 @@
const ActionViewer = () => import ('/imports/ui/properties/viewers/ActionViewer.vue');
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');
const ClassLevelViewer = () => import ('/imports/ui/properties/viewers/ClassLevelViewer.vue');
const ConstantViewer = () => import ('/imports/ui/properties/viewers/ConstantViewer.vue');
const DamageViewer = () => import ('/imports/ui/properties/viewers/DamageViewer.vue');
const DamageMultiplierViewer = () => import ('/imports/ui/properties/viewers/DamageMultiplierViewer.vue');
const EffectViewer = () => import ('/imports/ui/properties/viewers/EffectViewer.vue');
const FeatureViewer = () => import ('/imports/ui/properties/viewers/FeatureViewer.vue');
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');
const SkillViewer = () => import ('/imports/ui/properties/viewers/SkillViewer.vue');
const SavingThrowViewer = () => import ('/imports/ui/properties/viewers/SavingThrowViewer.vue');
const SlotViewer = () => import ('/imports/ui/properties/viewers/SlotViewer.vue');
const SlotFillerViewer = () => import ('/imports/ui/properties/viewers/SlotFillerViewer.vue');
const SpellListViewer = () => import ('/imports/ui/properties/viewers/SpellListViewer.vue');
const SpellViewer = () => import ('/imports/ui/properties/viewers/SpellViewer.vue');
const ToggleViewer = () => import ('/imports/ui/properties/viewers/ToggleViewer.vue');
const TriggerViewer = () => import ('/imports/ui/properties/viewers/TriggerViewer.vue');
import ActionViewer from '/imports/ui/properties/viewers/ActionViewer.vue';
import AdjustmentViewer from '/imports/ui/properties/viewers/AdjustmentViewer.vue';
import AttributeViewer from '/imports/ui/properties/viewers/AttributeViewer.vue';
import BuffViewer from '/imports/ui/properties/viewers/BuffViewer.vue';
import BuffRemoverViewer from '/imports/ui/properties/viewers/BuffRemoverViewer.vue';
import BranchViewer from '/imports/ui/properties/viewers/BranchViewer.vue';
import ContainerViewer from '/imports/ui/properties/viewers/ContainerViewer.vue';
import ClassViewer from '/imports/ui/properties/viewers/ClassViewer.vue';
import ClassLevelViewer from '/imports/ui/properties/viewers/ClassLevelViewer.vue';
import ConstantViewer from '/imports/ui/properties/viewers/ConstantViewer.vue';
import DamageViewer from '/imports/ui/properties/viewers/DamageViewer.vue';
import DamageMultiplierViewer from '/imports/ui/properties/viewers/DamageMultiplierViewer.vue';
import EffectViewer from '/imports/ui/properties/viewers/EffectViewer.vue';
import FeatureViewer from '/imports/ui/properties/viewers/FeatureViewer.vue';
import FolderViewer from '/imports/ui/properties/viewers/FolderViewer.vue';
import ItemViewer from '/imports/ui/properties/viewers/ItemViewer.vue';
import NoteViewer from '/imports/ui/properties/viewers/NoteViewer.vue';
import PointBuyViewer from '/imports/ui/properties/viewers/PointBuyViewer.vue';
import ProficiencyViewer from '/imports/ui/properties/viewers/ProficiencyViewer.vue';
import ReferenceViewer from '/imports/ui/properties/viewers/ReferenceViewer.vue';
import RollViewer from '/imports/ui/properties/viewers/RollViewer.vue';
import SkillViewer from '/imports/ui/properties/viewers/SkillViewer.vue';
import SavingThrowViewer from '/imports/ui/properties/viewers/SavingThrowViewer.vue';
import SlotViewer from '/imports/ui/properties/viewers/SlotViewer.vue';
import SlotFillerViewer from '/imports/ui/properties/viewers/SlotFillerViewer.vue';
import SpellListViewer from '/imports/ui/properties/viewers/SpellListViewer.vue';
import SpellViewer from '/imports/ui/properties/viewers/SpellViewer.vue';
import ToggleViewer from '/imports/ui/properties/viewers/ToggleViewer.vue';
import TriggerViewer from '/imports/ui/properties/viewers/TriggerViewer.vue';
export default {
action: ActionViewer,

View File

@@ -121,8 +121,7 @@
"quotes": [
"error",
"single"
],
"vuetify/no-deprecated-classes": "error"
]
}
}
}
}