Data changes to make attacks top level objects

This commit is contained in:
Stefan Zermatten
2019-09-19 16:03:46 +02:00
parent 63a20b2bef
commit 66d11d58f3
19 changed files with 368 additions and 201 deletions

View File

@@ -1,5 +1,6 @@
import SimpleSchema from 'simpl-schema';
import { ActionSchema } from '/imports/api/properties/Actions.js';
import { AttackSchema } from '/imports/api/properties/Attacks.js';
import { AttributeSchema } from '/imports/api/properties/Attributes.js';
import { StoredBuffSchema } from '/imports/api/properties/Buffs.js';
import { ClassLevelSchema } from '/imports/api/properties/ClassLevels.js';
@@ -19,6 +20,7 @@ import { ItemSchema } from '/imports/api/properties/Items.js';
const librarySchemas = {
action: ActionSchema,
attack: AttackSchema,
attribute: AttributeSchema,
buff: StoredBuffSchema,
classLevel: ClassLevelSchema,

View File

@@ -1,5 +1,6 @@
import SimpleSchema from 'simpl-schema';
import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import DamageSchema from '/imports/api/properties/subSchemas/DamageSchema.js';
import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js';
/*
@@ -19,7 +20,7 @@ let ActionSchema = new SimpleSchema({
},
// What time-resource is used to take the action in combat
// long actions take longer than 1 round to cast
type: {
actionType: {
type: String,
allowedValues: ['action', 'bonus', 'attack', 'reaction', 'free', 'long'],
defaultValue: 'action',
@@ -52,6 +53,13 @@ let ActionSchema = new SimpleSchema({
'adjustments.$': {
type: AdjustmentSchema,
},
damages: {
type: Array,
defaultValue: [],
},
'damages.$': {
type: DamageSchema,
},
// Buffs applied when taking this action
buffs: {
type: Array,

View File

@@ -1,5 +1,4 @@
import SimpleSchema from 'simpl-schema';
import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import { ActionSchema } from '/imports/api/properties/Actions.js';
// Attacks are special instances of actions
@@ -9,16 +8,9 @@ let AttackSchema = new SimpleSchema()
// What gets added to the d20 roll
rollBonus: {
type: String,
defaultValue: 'strength.modifier + proficiencyBonus',
optional: true,
},
// What damage does it do to the targets
adjustments: {
type: Array,
defaultValue: [],
},
'adjustments.$': {
type: AdjustmentSchema,
},
// If set reference an item whose quantity is reduced by 1 every time this
// attack is rolled
ammunition: {
@@ -27,12 +19,16 @@ let AttackSchema = new SimpleSchema()
optional: true,
},
// Set better defaults for the action
type: {
actionType: {
defaultValue: 'attack',
},
tags: {
type: Array,
defaultValue: ['attack'],
},
'tags.$': {
type: String,
},
});
export { AttackSchema };

View File

@@ -29,6 +29,16 @@ let EffectSchema = new SimpleSchema({
type: String,
optional: true,
},
statType: {
type: String,
allowedValues: [
'attribute',
'skill',
'roll',
'attack',
'damage',
],
},
//which stats the effect is applied to
stats: {
type: Array,

View File

@@ -1,4 +1,5 @@
import SimpleSchema from 'simpl-schema';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
ItemSchema = new SimpleSchema({
name: {
@@ -6,6 +7,13 @@ ItemSchema = new SimpleSchema({
optional: true,
defaultValue: "New Item",
},
// Variable name to reference this item as ammunition or in formulae
variableName: {
type: String,
regEx: VARIABLE_NAME_REGEX,
min: 3,
defaultValue: 'newAttribute',
},
// Plural name of the item, if there is more than one
plural: {
type: String,

View File

@@ -1,53 +0,0 @@
import SimpleSchema from 'simpl-schema';
/*
* RollModifiers are reason-value attached to rolls
* that modify their final value in some way
* These are separate from effects because they never cause a recomputation
* themselves
*/
let RollModifierSchema = new SimpleSchema({
name: {
type: String,
optional: true,
},
operation: {
type: String,
defaultValue: 'rollBonus',
allowedValues: [
'rollBonus',
'damageBonus',
'advantage',
],
},
calculation: {
type: String,
optional: true,
},
// which tags the modifier is applied to
// all tags must match
'tags.$': {
type: String,
optional: true,
},
});
const StoredRollModifierSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
}).extend(RollModifierSchema);
const ComputedRollModifierSchema = new SimpleSchema({
// The computed result of the effect
result: {
type: SimpleSchema.oneOf(Number, String),
optional: true,
},
}).extend(RollModifierSchema);
export { RollModifierSchema, StoredRollModifierSchema, ComputedRollModifierSchema };

View File

@@ -1,27 +0,0 @@
import SimpleSchema from 'simpl-schema';
import AdjustmentSchema from '/imports/api/creature/subSchemas/AdjustmentSchema.js';
import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js';
let RollResultSchema = new SimpleSchema ({
// Expression of whether or not to apply
comparison: {
type: String,
optional: true,
},
adjustments: {
type: Array,
defaultValue: [],
},
'adjustments.$': {
type: AdjustmentSchema,
},
buffs: {
type: Array,
defaultValue: [],
},
'buffs.$': {
type: StoredBuffWithIdSchema,
},
});
export { RollResultSchema };

View File

@@ -1,4 +1,5 @@
import SimpleSchema from 'simpl-schema';
import { RollResultsSchema } from '/imports/api/properties/subSchemas/RollResultsSchema.js'
/**
* Rolls are children to actions or other rolls, they are triggered with 0 or
@@ -33,6 +34,13 @@ let RollSchema = new SimpleSchema({
'tags.$': {
type: String,
},
results: {
type: Array,
defaultValue: [],
},
'results.$': {
type: RollResultsSchema,
},
});
export { RollSchema };

View File

@@ -1,6 +1,5 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
const AdjustmentSchema = new SimpleSchema({
_id: {
@@ -10,8 +9,8 @@ const AdjustmentSchema = new SimpleSchema({
if (!this.isSet) return Random.id();
}
},
// The roll that determines how much to damage the attribute
damage: {
// The roll that determines how much to change the attribute
adjustment: {
type: String,
optional: true,
defaultValue: '1',
@@ -31,12 +30,6 @@ const AdjustmentSchema = new SimpleSchema({
type: String,
optional: true,
},
// If set, the type of damage this adjustment causes
damageType: {
type: String,
allowedValues: DAMAGE_TYPES,
optional: true,
},
});
export default AdjustmentSchema;

View File

@@ -0,0 +1,36 @@
import SimpleSchema from 'simpl-schema';
import { Random } from 'meteor/random';
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
const DamageSchema = new SimpleSchema({
_id: {
type: String,
regEx: SimpleSchema.RegEx.Id,
autoValue(){
if (!this.isSet) return Random.id();
}
},
// The roll that determines how much to damage the attribute
damage: {
type: String,
optional: true,
defaultValue: '1d8 + strength.modifier',
},
// Who this adjustment applies to
target: {
type: String,
defaultValue: 'every',
allowedValues: [
'self', // the character who took the action
'each', // rolled once for `each` target
'every', // rolled once and applied to `every` target
],
},
damageType: {
type: String,
allowedValues: DAMAGE_TYPES,
defaultValue: 'slashing',
},
});
export default DamageSchema;

View File

@@ -0,0 +1,37 @@
import SimpleSchema from 'simpl-schema';
import AdjustmentSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import DamageSchema from '/imports/api/properties/subSchemas/AdjustmentSchema.js';
import { StoredBuffWithIdSchema } from '/imports/api/properties/Buffs.js';
let RollResultsSchema = new SimpleSchema ({
// Expression of whether or not to apply the roll
// Evaluates to an expression which gets compared to the roll
// or a number which the roll must equal
comparison: {
type: String,
optional: true,
},
damages: {
type: Array,
defaultValue: [],
},
'damages.$': {
type: DamageSchema,
},
adjustments: {
type: Array,
defaultValue: [],
},
'adjustments.$': {
type: AdjustmentSchema,
},
buffs: {
type: Array,
defaultValue: [],
},
'buffs.$': {
type: StoredBuffWithIdSchema,
},
});
export { RollResultsSchema };

View File

@@ -3,6 +3,10 @@ const PROPERTIES = Object.freeze({
icon: 'offline_bolt',
name: 'Action'
},
attack: {
icon: 'bolt',
name: 'Attack'
},
attribute: {
icon: 'donut_small',
name: 'Attribute'

View File

@@ -1,5 +1,5 @@
<template lang="html">
<div class="action-form">
<div :class="attackForm ? 'attack-form' : 'action-form'">
<text-field
label="Name"
:value="model.name"
@@ -8,17 +8,46 @@
:debounce-time="debounceTime"
/>
<smart-select
label="Type"
label="Action type"
:items="actionTypes"
:value="model.type"
:error-messages="errors.type"
:value="model.actionType"
:error-messages="errors.actionType"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['type'], value, ack})"
:hint="actionTypeHints[model.type]"
@change="(value, ack) => $emit('change', {path: ['actionType'], value, ack})"
:hint="actionTypeHints[model.actionType]"
:debounce-time="debounceTime"
/>
<text-field
label="Roll bonus"
v-if="attackForm"
:value="model.rollBonus"
@change="(value, ack) => $emit('change', {path: ['rollBonus'], value, ack})"
:error-messages="errors.rollBonus"
:debounce-time="debounceTime"
/>
<form-sections>
<form-section name="Damage">
<div class="caption">
Damage to deal when this action is taken
</div>
<damage-list-form
:model="model.damages"
:parent-target="model.target"
@change="({path, value, ack}) => $emit('change', {path: ['damages', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['damages', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['damages', ...path], ack})"
/>
</form-section>
<form-section name="Advanced">
<text-field
label="Ammunition"
hint="The name of the item used as ammunition"
v-if="attackForm"
:value="model.ammunition"
@change="(value, ack) => $emit('change', {path: ['ammunition'], value, ack})"
:error-messages="errors.ammunition"
:debounce-time="debounceTime"
/>
<v-combobox
label="Tags"
multiple
@@ -71,11 +100,10 @@
:debounce-time="debounceTime"
/>
</form-section>
<form-section name="Damage and Adjustments">
<form-section name="Adjustments">
<div class="caption">
Adjustments can be used to automatically spend resources or deal
damage when taking an action.
They apply damage to an attribute each time the action is taken.
Adjustments can be used to automatically spend resources or gain
resources when taking an action.
</div>
<adjustment-list-form
:model="model.adjustments"
@@ -105,6 +133,7 @@
<script>
import FormSection, {FormSections} from '/imports/ui/properties/forms/shared/FormSection.vue';
import AdjustmentListForm from '/imports/ui/properties/forms/AdjustmentListForm.vue';
import DamageListForm from '/imports/ui/properties/forms/DamageListForm.vue';
import BuffListForm from '/imports/ui/properties/forms/BuffListForm.vue';
export default {
@@ -112,6 +141,7 @@
FormSection,
FormSections,
AdjustmentListForm,
DamageListForm,
BuffListForm,
},
props: {
@@ -126,6 +156,9 @@
type: Object,
default: () => ({}),
},
attackForm: {
type: Boolean,
},
debounceTime: Number,
},
data(){

View File

@@ -0,0 +1,12 @@
<template lang="html">
<action-form attack-form v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
import ActionForm from '/imports/ui/properties/forms/ActionForm.vue';
export default {
components: {
ActionForm,
},
};
</script>

View File

@@ -0,0 +1,91 @@
<template lang="html">
<div>
<div class="layout row">
<text-field
label="Damage"
style="flex-basis: 300px;"
:value="model.damage"
@change="(value, ack) => $emit('change', {path: ['damage'], value, ack})"
:error-messages="errors.damage"
:debounce-time="debounceTime"
/>
<smart-select
label="Damage Type"
style="flex-basis: 200px;"
:items="DAMAGE_TYPES"
:value="model.damageType"
:error-messages="errors.damageType"
:menu-props="{auto: true}"
@change="(value, ack) => $emit('change', {path: ['damageType'], value, ack})"
:debounce-time="debounceTime"
/>
</div>
<smart-select
v-if="parentTarget == 'multipleTargets'"
label="Target"
:hint="targetOptionHint"
:items="targetOptions"
:value="model.target"
:error-messages="errors.target"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['target'], value, ack})"
:debounce-time="debounceTime"
/>
</div>
</template>
<script>
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
export default {
data(){return{
DAMAGE_TYPES,
}},
props: {
model: {
type: Object,
default: () => ({}),
},
errors: {
type: Object,
default: () => ({}),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
computed: {
targetOptions(){
return [
{
text: 'Self',
value: 'self',
}, {
text: 'Roll once for each target',
value: 'each',
}, {
text: 'Roll once and apply to every target',
value: 'every',
},
];
},
targetOptionHint(){
let hints = {
self: 'The damage will be applied to the character\'s own attribute when taking the action',
target: 'The damage will be applied to the target of the action',
each: 'The damage will be rolled separately for each of the targets of the action',
every: 'The damage will be rolled once and applied to each of the targets of the action',
};
if (this.parentTarget === 'singleTarget'){
hints.each = hints.target;
hints.every = hints.target;
}
return hints[this.model.target];
}
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -0,0 +1,75 @@
<template lang="html">
<div>
<v-slide-x-transition group>
<div
v-for="(damage, i) in model"
:key="damage._id || i"
>
<v-divider v-if="i !== 0"/>
<damage-form
class="mt-4"
:model="damage"
:parent-target="parentTarget"
@change="({path, value, ack}) => $emit('change', {path: [i, ...path], value, ack})"
/>
<div>
<v-btn outline icon large class="ma-3" @click="$emit('pull', {path: [i]})">
<v-icon>delete</v-icon>
</v-btn>
</div>
</div>
</v-slide-x-transition>
<div class="layout row justify-end">
<v-btn
:loading="addDamageLoading"
:disabled="addDamageLoading"
outline
@click="addDamage"
>
<v-icon>add</v-icon>
Add Damage
</v-btn>
</div>
</div>
</template>
<script>
import DamageForm from '/imports/ui/properties/forms/DamageForm.vue';
import DamageSchema from '/imports/api/properties/subSchemas/DamageSchema.js';
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
export default {
components: {
DamageForm,
},
data(){return {
addDamageLoading: false,
}},
methods: {
acknowledgeAddDamage(){
this.addDamageLoading = false;
},
addDamage(){
this.addDamageLoading = true;
this.$emit('push', {
path: [],
value: DamageSchema.clean({}),
ack: this.acknowledgeAddDamage,
});
},
},
props: {
model: {
type: Array,
default: () => ([]),
},
parentTarget: {
type: String,
},
debounceTime: Number,
},
}
</script>
<style lang="css" scoped>
</style>

View File

@@ -1,12 +1,23 @@
<template lang="html">
<div class="attribute-form">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<div class="item-form">
<div class="layout row wrap">
<text-field
label="Name"
:value="model.name"
@change="(value, ack) => $emit('change', {path: ['name'], value, ack})"
:error-messages="errors.name"
:debounce-time="debounceTime"
/>
<text-field
label="Variable name"
:value="model.variableName"
style="flex-basis: 300px;"
@change="(value, ack) => $emit('change', {path: ['variableName'], value, ack})"
hint="Use this name in formulae to reference this attribute"
:error-messages="errors.variableName"
:debounce-time="debounceTime"
/>
</div>
<text-field
label="Plural name"
:value="model.plural"

View File

@@ -2,27 +2,10 @@
<div class="roll-form">
<text-field
label="Roll"
hint="The roll will be calculated using the rolling character's stats"
:value="model.roll"
@change="(value, ack) => $emit('change', {path: ['roll'], value, ack})"
:error-messages="errors.roll"
:debounce-time="debounceTime"
/>
<text-field
label="Target number"
hint="The target number or stat to meet or exceed, calculated from the target's stats"
:value="model.targetNumber"
@change="(value, ack) => $emit('change', {path: ['targetNumber'], value, ack})"
:error-messages="errors.targetNumber"
:debounce-time="debounceTime"
/>
<smart-select
label="Type"
:items="rollTypes"
:value="model.rollType"
:error-messages="errors.rollType"
:menu-props="{auto: true, lazy: true}"
@change="(value, ack) => $emit('change', {path: ['rollType'], value, ack})"
prefix="d20+"
:value="model.rollBonus"
@change="(value, ack) => $emit('change', {path: ['rollBonus'], value, ack})"
:error-messages="errors.rollBonus"
:debounce-time="debounceTime"
/>
<v-combobox
@@ -36,68 +19,6 @@
/>
<form-sections>
<form-section name="Advanced">
<v-switch
label="Only roll if the parent roll misses"
:input-value="model.onMiss"
:error-messages="errors.onMiss"
@change="e => $emit('change', {path: ['onMiss'], value: !!e})"
/>
<v-switch
label="Swap who wins ties"
:input-value="model.invertTies"
:error-messages="errors.invertTies"
@change="e => $emit('change', {path: ['invertTies'], value: !!e})"
/>
</form-section>
<form-section name="Damage and Adjustments on Hit">
<div class="caption">
Adjustments can be used to automatically spend resources or deal
damage when taking an roll.
These apply when a roll suceeds.
</div>
<adjustment-list-form
:model="model.hit.adjustments"
@change="({path, value, ack}) => $emit('change', {path: ['hit', 'adjustments', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['hit', 'adjustments', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['hit', 'adjustments', ...path], ack})"
/>
</form-section>
<form-section name="Buffs on Hit">
<div class="caption">
Buffs apply temporary effects to characters when the roll succeeds.
</div>
<buff-list-form
:model="model.hit.buffs"
:stored="stored"
@change="({path, value, ack}) => $emit('change', {path: ['hit', 'buffs', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['hit', 'buffs', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['hit', 'buffs', ...path], ack})"
/>
</form-section>
<form-section name="Damage and Adjustments on Miss">
<div class="caption">
Adjustments can be used to automatically spend resources or deal
damage when taking an roll.
These apply when a roll fails.
</div>
<adjustment-list-form
:model="model.miss.adjustments"
@change="({path, value, ack}) => $emit('change', {path: ['miss', 'adjustments', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['miss', 'adjustments', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['miss', 'adjustments', ...path], ack})"
/>
</form-section>
<form-section name="Buffs on Miss">
<div class="caption">
Buffs apply temporary effects to characters when the roll fails.
</div>
<buff-list-form
:model="model.miss.buffs"
:stored="stored"
@change="({path, value, ack}) => $emit('change', {path: ['miss', 'buffs', ...path], value, ack})"
@push="({path, value, ack}) => $emit('push', {path: ['miss', 'buffs', ...path], value, ack})"
@pull="({path, ack}) => $emit('pull', {path: ['miss', 'buffs', ...path], ack})"
/>
</form-section>
</form-sections>
</div>

View File

@@ -1,4 +1,5 @@
import ActionForm from '/imports/ui/properties/forms/ActionForm.vue';
import AttackForm from '/imports/ui/properties/forms/AttackForm.vue';
import AttributeForm from '/imports/ui/properties/forms/AttributeForm.vue';
import BuffForm from '/imports/ui/properties/forms/BuffForm.vue';
import ContainerForm from '/imports/ui/properties/forms/ContainerForm.vue';
@@ -18,6 +19,7 @@ import SpellForm from '/imports/ui/properties/forms/SpellForm.vue';
export default {
action: ActionForm,
attack: AttackForm,
attribute: AttributeForm,
buff: BuffForm,
container: ContainerForm,