Merge branch 'develop' into feature-tabletop

This commit is contained in:
ThaumRystra
2023-09-24 19:10:04 +02:00
47 changed files with 1166 additions and 562 deletions

View File

@@ -59,7 +59,8 @@ export default {
};
},
meteor: {
notes(){
notes() {
// Get all the notes that aren't children of group folders or of other displayed notes
const folderIds = CreatureProperties.find({
'ancestors.id': this.creatureId,
type: 'folder',
@@ -68,8 +69,8 @@ export default {
removed: { $ne: true },
inactive: { $ne: true },
}, { fields: { _id: 1 } }).map(folder => folder._id);
return CreatureProperties.find({
const noteFilter = {
'ancestors.id': {
$eq: this.creatureId,
},
@@ -77,9 +78,20 @@ export default {
$nin: folderIds,
},
type: 'note',
removed: {$ne: true},
inactive: {$ne: true},
}, {
removed: { $ne: true },
inactive: { $ne: true },
};
const noteIds = CreatureProperties.find(noteFilter, {
sort: { order: 1 },
fields: {_id: 1},
}).map(note => note._id);
noteFilter['ancestors.id'] = {
$eq: this.creatureId,
$nin: noteIds,
}
return CreatureProperties.find(noteFilter, {
sort: {order: 1},
});
},

View File

@@ -32,7 +32,10 @@
light
>
<div class="page pa-3">
<div class="title-block px-3 d-flex align-center">
<div
class="title-block px-3 d-flex align-center"
style="page-break-after: avoid;"
>
<div class="logo-background" />
<div class="creature-name mr-3">
{{ creature.name }}
@@ -59,14 +62,18 @@
</div>
<div
class="text-right mt-3 mr-4"
style="font-size: 8pt; margin-bottom: -4px;"
style="font-size: 8pt; margin-bottom: -4px; page-break-after: avoid;"
>
{{ creatureUrl }}
</div>
<printed-stats :creature-id="creatureId" />
<printed-inventory :creature-id="creatureId" />
<printed-inventory
:creature-id="creatureId"
class="page-break-before"
/>
<printed-spells
v-if="!creature.settings.hideSpellsTab"
class="page-break-before"
:creature-id="creatureId"
/>
</div>
@@ -234,7 +241,7 @@ export default {
.character-sheet-printed {
background: white;
color: black;
font-size: 11pt;
font-size: 10pt;
}
.character-sheet-printed * {
@@ -247,17 +254,31 @@ export default {
padding: 4px;
}
.character-sheet-printed p {
margin-bottom: 8px;
}
.character-sheet-printed .double-border > .label:first-child {
margin-bottom: 8px;
}
.character-sheet-printed .column-layout, .character-sheet-printed .column-layout.wide-columns {
position:relative;
width: 100%;
widows: 0;
orphans: 0;
-webkit-column-fill: balance-all;
column-fill: balance-all;
column-fill: balance;
padding: 0;
}
.character-sheet-printed .column-layout {
column-width: 200px;
}
.character-sheet-printed .column-layout>div {
position:relative;
margin-top: 4px;
margin-bottom: 4px;
}
.character-sheet-printed .column-layout > div > * {
page-break-inside: avoid;
@@ -267,9 +288,10 @@ export default {
opacity: 1 !important;
}
.character-sheet-printed .creature-name {
font-size: 24pt;
font-size: 16pt;
background-color: white;
}
.character-sheet-printed .logo-background {
width: 60px;
height: 60px;
@@ -284,13 +306,20 @@ export default {
max-width: unset;
}
.character-sheet-printed .tree-node-title {
min-height: unset !important;
}
.character-sheet-printed .double-border {
position: relative;
padding: 11px 10px;
border-style: solid;
border-width: 11px 10px;
border-image-source: url(/images/print/doubleLineImageBorder.png);
border-image-slice: 110 126 fill;
border-image-width: 16px;
border-image-repeat: stretch;
box-decoration-break: clone;
page-break-inside: avoid;
}
.character-sheet-printed .octagon-border {
@@ -298,6 +327,8 @@ export default {
padding: 4px 20px;
border-image: url(/images/print/octagonBorder.png) 124 118 fill;
border-image-width: 22px;
box-decoration-break: clone;
page-break-inside: avoid;
}
.character-sheet-printed .span-all {
@@ -311,7 +342,7 @@ export default {
.character-sheet-printed .stats .label {
font-size: 10pt;
font-variant: small-caps;
font-variant: all-small-caps
}
.character-sheet-printed .label {
@@ -322,6 +353,15 @@ export default {
.character-sheet-printed .span-all {
column-span: all;
display: block;
}
.character-sheet-printed .page-break-before {
page-break-before: always;
}
.character-sheet-printed .avoid-page-break-after {
page-break-after: avoid;
}
@media screen {
@@ -337,7 +377,7 @@ export default {
@media print {
@page {
size: auto;
margin: 8mm 8mm 8mm 8mm;
margin: 8mm;
}
body {
margin: 0;

View File

@@ -2,83 +2,72 @@
<div
class="inventory"
>
<column-layout wide-columns>
<div class="span-all">
<div class="double-border">
<div class="label text-center">
Inventory
</div>
<div class="d-flex inventory-stat">
<v-icon>$vuetify.icons.injustice</v-icon>
Weight Carried:
{{ weightCarried }} lb
</div>
<div class="d-flex inventory-stat">
<v-icon>$vuetify.icons.cash</v-icon>
Net worth:
<coin-value
class="ml-2"
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
/>
</div>
<div class="d-flex inventory-stat">
<v-icon>$vuetify.icons.spell</v-icon>
Items attuned:
{{ variables.itemsAttuned && variables.itemsAttuned.value }}
</div>
</div>
<div class="double-border my-2">
<div class="label text-center">
Inventory
</div>
<div class="span-all">
<div class="octagon-border label text-center">
Equipped
</div>
<div class="d-flex inventory-stat">
<v-icon>$vuetify.icons.injustice</v-icon>
Weight Carried:
{{ weightCarried }} lb
</div>
<div
v-for="item in equippedItems"
:key="item._id"
>
<printed-item
class="double-border"
:model="item"
<div class="d-flex inventory-stat">
<v-icon>$vuetify.icons.cash</v-icon>
Net worth:
<coin-value
class="ml-2"
:value="variables && variables.valueTotal && variables.valueTotal.value|| 0"
/>
</div>
<div class="span-all">
<div class="octagon-border label text-center">
Carried
</div>
</div>
<div
v-for="item in carriedItems"
:key="item._id"
v-if="variables.itemsAttuned && variables.itemsAttuned.value"
class="d-flex inventory-stat"
>
<v-icon>$vuetify.icons.spell</v-icon>
Items attuned:
{{ variables.itemsAttuned && variables.itemsAttuned.value }}
</div>
</div>
<div class="double-border my-2">
<div class="label text-center">
Equipped
</div>
<column-layout wide-columns>
<printed-item
class="double-border"
v-for="item in equippedItems"
:key="item._id"
:model="item"
/>
</column-layout>
</div>
<div class="double-border my-2">
<div class="label text-center">
Carried
</div>
<template
v-for="container in containersWithoutAncestorContainers"
>
<div
:key="container._id"
class="span-all"
>
<printed-container
class="octagon-border"
:model="container"
/>
</div>
<div
<column-layout wide-columns>
<printed-item
v-for="item in carriedItems"
:key="item._id"
:model="item"
/>
</column-layout>
</div>
<div
v-for="container in containersWithoutAncestorContainers"
:key="container._id"
class="double-border my-2"
>
<printed-container
:model="container"
/>
<column-layout wide-columns>
<printed-item
v-for="item in container.items"
:key="item._id"
>
<printed-item
class="double-border"
:model="item"
/>
</div>
</template>
</column-layout>
:model="item"
/>
</column-layout>
</div>
</div>
</template>
@@ -90,7 +79,7 @@ import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/
import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js';
import CoinValue from '/imports/client/ui/components/CoinValue.vue';
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
import PrintedItem from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedItem.vue';
import PrintedLineItem from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedLineItem.vue';
import PrintedContainer from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedContainer.vue';
import CreatureVariables from '/imports/api/creature/creatures/CreatureVariables.js';
@@ -98,7 +87,7 @@ export default {
components: {
ColumnLayout,
CoinValue,
PrintedItem,
PrintedItem: PrintedLineItem,
PrintedContainer,
},
props: {
@@ -233,15 +222,17 @@ export default {
</script>
<style lang="css" scoped>
.label {
font-size: 14pt;
font-size: 12pt;
font-variant: small-caps;
flex-grow: 1;
}
.inventory .double-border {
box-decoration-break: slice;
}
.inventory-stat {
font-size: 12pt;
font-size: 11pt;
line-height: 32px;
}
.inventory-stat > .v-icon {

View File

@@ -2,37 +2,38 @@
<div
class="spells"
>
<column-layout wide-columns>
<div class="span-all">
<div class="label text-center octagon-border">
Spells
</div>
</div>
<div
class="label text-center octagon-border my-2 avoid-page-break-after"
>
Spells
</div>
<column-layout
v-if="spellsWithoutList && spellsWithoutList.length"
wide-columns
>
<div
v-for="spell in spellsWithoutList"
:key="spell._id"
>
<printed-spell :model="spell" />
</div>
<template
v-for="spellList in spellListsWithoutAncestorSpellLists"
>
<div
:key="spellList._id"
class="span-all"
>
<printed-spell-list
:model="spellList"
/>
</div>
</column-layout>
<div
v-for="spellList in spellListsWithoutAncestorSpellLists"
:key="spellList._id"
>
<printed-spell-list
:model="spellList"
/>
<column-layout wide-columns>
<div
v-for="spell in spellList.spells"
:key="spell._id"
>
<printed-spell :model="spell" />
</div>
</template>
</column-layout>
</column-layout>
</div>
</div>
</template>

View File

@@ -1,21 +1,32 @@
<template lang="html">
<div class="stats">
<column-layout>
<div
class="d-flex wrap justify-space-between px-2 pt-3 pb-1"
style="page-break-after: avoid"
>
<div
v-for="ability in abilities"
:key="ability._id"
>
<div
class="ability"
class="ability ma-0"
>
<div class="score">
<div class="double-border top big-number">
<template v-if="creature.settings.swapScoresAndMods">
{{ ability.total }}
</template>
<template v-else>
{{ numberToSignedString(ability.modifier) }}
</template>
<div class="double-border top">
<div
class="label text-center mb-0"
style="line-height: 16px"
>
{{ ability.name }}
</div>
<div class="big-number mb-1">
<template v-if="creature.settings.swapScoresAndMods">
{{ ability.total }}
</template>
<template v-else>
{{ numberToSignedString(ability.modifier) }}
</template>
</div>
</div>
<div class="bottom">
<template v-if="creature.settings.swapScoresAndMods">
@@ -26,12 +37,10 @@
</template>
</div>
</div>
<div class="double-border name label">
{{ ability.name }}
</div>
</div>
</div>
</div>
<column-layout>
<div
v-for="toggle in toggles"
:key="toggle._id"
@@ -102,10 +111,15 @@
:key="healthBar._id"
>
<div class="double-border">
<div class="label">
Total: {{ healthBar.total }}
<div>
<b>
Total:
</b>
<span>
{{ healthBar.total }}
</span>
</div>
<div style="height: 60px;" />
<div style="height: 40px;" />
<div
style="text-align: center;"
class="label"
@@ -125,9 +139,9 @@
>
<div class="double-border">
<div>
<span class="label">
<b>
Total:
</span>
</b>
<span
v-for="hitDie in hitDice"
:key="hitDie._id"
@@ -136,7 +150,7 @@
{{ hitDie.total }}{{ hitDie.hitDiceSize }}
</span>
</div>
<div style="height: 60px;" />
<div style="height: 40px;" />
<div
style="text-align: center;"
class="label"
@@ -156,7 +170,7 @@
>
<div
v-if="resource.total <= 8"
class="label"
class="label mb-0"
>
{{ resource.name }}
</div>
@@ -169,6 +183,7 @@
<div
v-if="resource.total <= 8"
class="d-flex justify-end"
style="margin-top: -4px"
>
<div
v-for="i in resource.total"
@@ -185,6 +200,77 @@
</div>
</div>
<div
v-if="savingThrows.length"
>
<div
class="double-border"
>
<printed-skill
v-for="save in savingThrows"
:key="save._id"
:model="save"
:data-id="save._id"
/>
<div
v-for="(effect) in saveConditionals"
:key="effect._id"
class="mt-2"
>
* {{ effect.text }}
</div>
<div class="label text-center">
Saving Throws
</div>
</div>
</div>
<div
v-if="skills.length"
>
<div
class="double-border"
>
<printed-skill
v-for="skill in skills"
:key="skill._id"
:model="skill"
:data-id="skill._id"
/>
<div
v-for="(effect) in skillConditionals"
:key="effect._id"
class="mt-2"
>
* {{ effect.text }}
</div>
<div class="label text-center">
Skills
</div>
</div>
</div>
<div>
<div
class="double-border"
>
<p>
<b>Weapons:</b> {{ weapons.map(p => p.name).join(', ') }}
</p>
<p>
<b>Armor:</b> {{ armors.map(p => p.name).join(', ') }}
</p>
<p>
<b>Tools:</b> {{ tools.map(p => p.name).join(', ') }}
</p>
<p>
<b>Languages:</b> {{ languages.map(p => p.name).join(', ') }}
</p>
<div class="label text-center">
Proficiencies
</div>
</div>
</div>
<div
v-if="spellSlots && spellSlots.length"
>
@@ -220,114 +306,6 @@
</div>
</div>
<div
v-if="savingThrows.length"
>
<div
class="double-border"
>
<printed-skill
v-for="save in savingThrows"
:key="save._id"
:model="save"
:data-id="save._id"
/>
<div class="label text-center">
Saving Throws
</div>
</div>
</div>
<div
v-if="skills.length"
>
<div
class="double-border"
>
<printed-skill
v-for="skill in skills"
:key="skill._id"
:model="skill"
:data-id="skill._id"
/>
<div class="label text-center">
Skills
</div>
</div>
</div>
<div
v-if="weapons && weapons.length"
>
<div
class="double-border"
>
<printed-skill
v-for="weapon in weapons"
:key="weapon._id"
hide-modifier
:model="weapon"
:data-id="weapon._id"
/>
<div class="label text-center">
Weapons
</div>
</div>
</div>
<div
v-if="armors && armors.length"
>
<div
class="double-border"
>
<printed-skill
v-for="armor in armors"
:key="armor._id"
hide-modifier
:model="armor"
:data-id="armor._id"
/>
<div class="label text-center">
Armor
</div>
</div>
</div>
<div
v-if="tools && tools.length"
>
<div
class="double-border"
>
<printed-skill
v-for="tool in tools"
:key="tool._id"
hide-modifier
:model="tool"
:data-id="tool._id"
/>
<div class="label text-center">
Tools
</div>
</div>
</div>
<div
v-if="languages && languages.length"
>
<div
class="double-border"
>
<printed-skill
v-for="language in languages"
:key="language._id"
hide-modifier
:model="language"
:data-id="language._id"
/>
<div class="label text-center">
Languages
</div>
</div>
</div>
<div
v-for="note in notes"
:key="note._id"
@@ -381,6 +359,7 @@ import numberToSignedString from '../../../../../api/utility/numberToSignedStrin
import PrintedSkill from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue';
import PrintedDamageMultipliers from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue';
import PropertyDescription from '/imports/client/ui/properties/viewers/shared/PropertyDescription.vue';
import { uniqBy } from 'lodash';
const getProperties = function (creature, filter, options = {
sort: { order: 1 }
@@ -483,9 +462,31 @@ export default {
savingThrows() {
return getSkillOfType(this.creature, 'save');
},
saveConditionals(){
const conditionals = [];
this.savingThrows?.forEach(prop => {
prop?.effects?.forEach(effect => {
if (effect.operation === 'conditional') {
conditionals.push(effect);
}
});
});
return uniqBy(conditionals, '_id');
},
skills() {
return getSkillOfType(this.creature, 'skill');
},
skillConditionals(){
const conditionals = [];
this.skills?.forEach(prop => {
prop?.effects?.forEach(effect => {
if (effect.operation === 'conditional') {
conditionals.push(effect);
}
});
});
return uniqBy(conditionals, '_id');
},
tools() {
return getSkillOfType(this.creature, 'tool');
},
@@ -517,7 +518,15 @@ export default {
return getProperties(this.creature, { type: 'feature' });
},
notes(){
return getProperties(this.creature, { type: 'note', summary: {$exists: true} });
const allNoteIds = getProperties(this.creature, {
type: 'note',
}).map(note => note._id);
const topLevelNotes = getProperties(this.creature, {
type: 'note',
summary: { $exists: true },
'ancestor.id': {$nin: allNoteIds}
});
return topLevelNotes;
},
},
methods: {
@@ -557,10 +566,9 @@ export default {
align-items: center;
}
.ability .top {
min-width: 64px;
min-width: 86px;
text-align: center;
margin-bottom: -10px;
padding: 14px;
margin: 4px 4px -10px;
z-index: 1;
}
.ability .bottom {

View File

@@ -3,36 +3,21 @@
class="action-card"
:class="cardClasses"
>
<div class="label text-center">
{{ actionTypeName }}
</div>
<div class="d-flex align-center">
<div class="d-flex align-center mb-2">
<div class="roll-bonus">
<template v-if="!onHitDamage && rollBonus">
{{ rollBonus }}
</template>
</div>
<div class="action-title text-center flex-grow-1">
{{ model.name || propertyName }}
</div>
<div class="avatar">
<div
v-if="rollBonus"
>
<template v-if="rollBonus && !rollBonusTooLong">
{{ rollBonus }}
</template>
<property-icon
v-else
:model="model"
color="rgba(0,0,0,0.7)"
/>
</div>
<property-icon
v-else
:model="model"
color="rgba(0,0,0,0.7)"
/>
</div>
<div
class="action-header flex d-flex column justify-center pl-1"
>
<div class="action-title my-1">
{{ model.name || propertyName }}
</div>
</div>
</div>
<div
v-if="Number.isFinite(model.uses)"
@@ -40,7 +25,7 @@
>
{{ model.uses }} uses
</div>
<div class="pb-3">
<div>
<div
v-if="model.resources && model.resources.attributesConsumed.length ||
model.resources.itemsConsumed.length"
@@ -65,20 +50,41 @@
<template v-if="model.summary">
<markdown-text :markdown="model.summary.value || model.summary.text" />
</template>
<v-divider v-if="children && children.length" />
<div
v-if="onHitDamage"
>
<span class="damage">
{{ rollBonus }}
</span>
<span>
to hit
</span>
</div>
<div v-if="onHitDamage">
<span class="damage">
{{ onHitDamage.damage }}
</span>
<span>
{{ onHitDamage.suffix }}
</span>
</div>
<tree-node-list
v-if="children && children.length"
v-else-if="children && children.length"
start-expanded
show-external-details
:children="children"
@selected="e => $emit('sub-click', e)"
/>
</div>
<div class="action-subtitle text-center">
{{ actionTypeName }}
</div>
</div>
</template>
<script lang="js">
import { getPropertyName } from '/imports/constants/PROPERTIES.js';
import numberToSignedString from '../../../../../../api/utility/numberToSignedString.js';
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
import AttributeConsumedView from '/imports/client/ui/properties/components/actions/AttributeConsumedView.vue';
import ItemConsumedView from '/imports/client/ui/properties/components/actions/ItemConsumedView.vue';
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
@@ -87,6 +93,8 @@ import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { some } from 'lodash';
import applyEffectsToCalculationParseNode from '/imports/api/engine/actions/applyPropertyByType/shared/applyEffectsToCalculationParseNode.js';
import resolve, { Context, toString } from '/imports/parser/resolve.js';
export default {
components: {
@@ -148,7 +156,24 @@ export default {
'free': 'Free Action',
'long': 'Long Action'
}[this.model.actionType] || this.model.actionType
}
},
onHitDamage() {
/**
* Only match a property who has exactly one to-hit child with one damage under that
*/
if (this.children?.length !== 1) return;
if (this.children[0]?.node?.type !== 'branch') return;
if (this.children[0].children?.length !== 1) return;
if (this.children[0].children[0]?.node?.type !== 'damage') return;
if (this.children[0].children[0].children?.length !== 0) return;
const damage = this.children[0].children[0]?.node;
applyEffectsToCalculationParseNode(damage.amount);
const { result } = resolve('compile', damage.amount.parseNode, {});
return {
damage: toString(result),
suffix: damage.damageType + (damage.damageType !== 'healing' ? ' damage ' : '')
};
},
},
meteor: {
children() {
@@ -187,28 +212,39 @@ export default {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
transform 0.075s ease;
}
.avatar {
.roll-bonus {
font-size: 18pt;
text-align: center;
min-width: 40px;
min-height: 40px;
flex-basis: 24px;
}
.avatar {
min-width: 24px;
min-height: 24px;
line-height: 24px;
}
.label {
font-size: 10pt;
font-variant: small-caps;
font-variant: all-small-caps;
flex-grow: 1;
}
.damage {
font-size: 12pt;
font-weight: 500;
}
.action-title {
font-size: 16px;
font-weight: 400;
line-height: 24px;
position: relative;
text-align: left;
transition: .3s cubic-bezier(.25, .8, .5, 1);
width: 100%;
font-size: 12pt;
font-weight: 600;
min-height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-variant: all-small-caps;
}
.action-subtitle {
font-variant: all-small-caps;
font-size: 11pt;
}
.resources {

View File

@@ -88,11 +88,6 @@
</div>
</div>
</div>
<property-description
text
:model="model.description"
/>
</div>
</template>

View File

@@ -0,0 +1,121 @@
<template lang="html">
<div class="printed-line-item d-flex align-start mb-0">
<div class="quantity">
{{ model.quantity !== 1 ? model.quantity : undefined }}
</div>
<div class="text flex-grow-1">
{{ title }}
<template v-if="attunementText">
({{ attunementText }})
</template>
</div>
<div class="weight-value d-flex flex-column align-end">
<div
v-if="model.quantity !== 1"
class="each d-flex align-center"
>
<coin-value
v-if="model.value"
class="value text-no-wrap"
:value="model.value"
/>
<div
class="weight text-no-wrap"
>
<template
v-if="model.weight"
>
{{ model.weight }} lb
</template>
</div>
</div>
<div class="total d-flex align-center">
<coin-value
v-if="totalValue"
class="value text-no-wrap"
:value="totalValue"
/>
<div
class="weight text-no-wrap"
>
<template
v-if="model.weight"
>
{{ totalWeight }} lb
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="js">
import PROPERTIES from '/imports/constants/PROPERTIES.js';
import stripFloatingPointOddities from '/imports/api/engine/computation/utility/stripFloatingPointOddities.js';
import CoinValue from '/imports/client/ui/components/CoinValue.vue';
export default {
components: {
CoinValue,
},
props: {
model: {
type: Object,
required: true,
},
},
computed: {
title() {
let model = this.model;
if (!model) return;
if (model.quantity !== 1) {
if (model.plural) {
return model.plural;
} else if (model.name) {
return model.name;
}
} else if (model.name) {
return model.name;
}
let prop = PROPERTIES[model.type]
return prop && prop.name;
},
totalValue() {
return stripFloatingPointOddities(this.model.value * this.model.quantity);
},
totalWeight() {
return stripFloatingPointOddities(this.model.weight * this.model.quantity);
},
attunementText() {
if (this.model.requiresAttunement) {
if (this.model.attuned) return 'Attuned';
return 'Requires attunement';
}
return undefined;
}
},
}
</script>
<style lang="css" scoped>
.quantity {
flex-basis: 32px;
font-weight: 700;
text-align: end;
padding-right: 8px;
}
.each {
font-weight: 300;
}
.total {
font-weight: 500;
}
.value {
min-width: 40px;
text-align: end;
}
.weight {
min-width: 40px;
text-align: end;
}
</style>

View File

@@ -11,7 +11,7 @@
:value="model.proficiency"
class="prof-icon"
/>
<div class="prof-mod ml-2 mr-4 text-right">
<div class="prof-mod mr-3 text-right">
{{ displayedModifier }}
</div>
<v-icon
@@ -88,7 +88,7 @@ export default {
<style lang="css" scoped>
.printed-skill{
min-height: 30px;
min-height: 0;
}
.prof-icon {

View File

@@ -2,35 +2,52 @@
<div
class="double-border"
>
<div
v-if="model.name"
class="label"
>
{{ model.name }}
<div class="d-flex align-center mb-2">
<div class="spell-level">
<div
v-if="model.level"
class="spell-level-number"
>
{{ romanLevel }}
</div>
</div>
<div class="spell-title text-center flex-grow-1">
{{ model.name || propertyName }}
</div>
<div class="avatar">
<property-icon
:model="model"
color="rgba(0,0,0,0.7)"
/>
</div>
</div>
<div v-if="model.level">
{{ levelText }} {{ model.school }} {{ model.ritual ? '(ritual)' : '' }}
</div>
<div v-else>
{{ model.school }} cantrip
{{ model.school }} cantrip {{ model.ritual ? '(ritual)' : '' }}
</div>
<div
v-if="rollBonus"
>
<b>To hit:</b> {{ rollBonus }}
</div>
<div>
Casting Time: {{ model.castingTime }}
<b>Casting time:</b> {{ model.castingTime }}
</div>
<div>
Range: {{ model.range }}
<b>Range:</b> {{ model.range }}
</div>
<div>
Components: {{ spellComponents }}
<b>Components:</b> {{ spellComponents }}
</div>
<div>
Duration: {{ model.duration }}
<div class="mb-4">
<b>Duration:</b> {{ model.duration }}
</div>
<property-description
text
:model="model.summary"
/>
<v-divider class="my-2" />
<property-description
text
:model="model.description"
@@ -39,7 +56,10 @@
</template>
<script lang="js">
import PropertyIcon from '/imports/client/ui/properties/shared/PropertyIcon.vue';
import PropertyDescription from '/imports/client/ui/properties/viewers/shared/PropertyDescription.vue';
import numberToSignedString from '/imports/api/utility/numberToSignedString.js';
import romanize from '/imports/client/ui/utility/romanize.js';
const levelText = [
'cantrip', '1st-level', '2nd-level', '3rd-level', '4th-level', '5th-level',
@@ -48,6 +68,7 @@ const levelText = [
export default {
components: {
PropertyIcon,
PropertyDescription,
},
props: {
@@ -58,15 +79,21 @@ export default {
},
computed: {
levelText() {
return levelText[this.model.level]
return levelText[this.model.level] || `level ${this.model.level}`;
},
romanLevel() {
return romanize(this.model.level) || this.model.level;
},
rollBonus() {
if (!this.model.attackRoll) return;
return numberToSignedString(this.model.attackRoll.value);
},
spellComponents() {
let components = [];
if (this.model.ritual) components.push('Ritual');
if (this.model.concentration) components.push('Concentration');
if (this.model.verbal) components.push('Verbal');
if (this.model.somatic) components.push('Somatic');
if (this.model.material) components.push(`Material (${this.model.material})`);
if (this.model.concentration) components.push('C');
if (this.model.verbal) components.push('V');
if (this.model.somatic) components.push('S');
if (this.model.material) components.push(`M (${this.model.material})`);
return components.join(', ');
},
}
@@ -74,9 +101,24 @@ export default {
</script>
<style lang="css" scoped>
.label {
.spell-level {
width: 24px;
}
.spell-level-number {
font-size: 18pt;
}
.avatar {
min-width: 24px;
min-height: 24px;
line-height: 24px;
}
.spell-title {
font-size: 14pt;
font-variant: all-small-caps;
font-weight: 600;
min-height: 24px;
display: flex;
align-items: center;
justify-content: center;
font-variant: all-small-caps;
}
</style>

View File

@@ -1,5 +1,8 @@
<template>
<div class="octagon-border">
<div
class="octagon-border my-1"
style="page-break-after: avoid;"
>
<div class="label text-center">
{{ model.name }}
</div>

View File

@@ -31,25 +31,91 @@
@change="change('damageType', ...arguments)"
/>
</v-col>
<v-col cols="12">
<smart-toggle
label="Target creature"
:value="model.target"
:options="[
{name: 'Action Target', value: 'target'},
{name: 'Self', value: 'self'},
]"
:error-messages="errors.target"
@change="change('target', ...arguments)"
/>
</v-col>
<v-col cols="12">
<smart-switch
class="mt-0"
label="Saving throw"
:value="!!model.save"
:error-messages="errors.save"
@change="(val, ack) => $emit('change', {
path: ['save'],
value: val ? {} : undefined,
ack
})"
/>
</v-col>
</v-row>
<smart-toggle
label="Target creature"
:value="model.target"
:options="[
{name: 'Action Target', value: 'target'},
{name: 'Self', value: 'self'},
]"
:error-messages="errors.target"
@change="change('target', ...arguments)"
/>
<v-expand-transition>
<v-row
v-if="model.save"
dense
>
<v-col
cols="12"
md="6"
>
<computed-field
label="DC"
hint="Saving throw DC"
:model="model.save.dc"
:error-messages="errors['save.dc']"
@change="({path, value, ack}) =>
$emit('change', {path: ['save', 'dc', ...path], value, ack})"
/>
</v-col>
<v-col
cols="12"
md="6"
>
<smart-combobox
label="Save"
hint="Which stat the saving throw targets"
:value="model.save.stat"
:items="saveList"
:error-messages="errors['save.stat']"
@change="(value, ack) =>
$emit('change', {path: ['save', 'stat'], value, ack})"
/>
</v-col>
<v-col cols="12">
<computed-field
v-if="!!model.save"
label="Damage on successful save"
hint="Use &quot;~damage&quot; to reference the damage that would normally be dealt"
placeholder="Half damage"
persistent-placeholder
:model="model.save.damageFunction"
:error-messages="errors['save.damageFunction']"
@change="({path, value, ack}) =>
$emit('change', {path: ['save', 'damageFunction', ...path], value, ack})"
/>
</v-col>
</v-row>
</v-expand-transition>
<form-sections type="damage">
<form-section name="Log">
<smart-switch
label="Don't show in log"
:value="model.silent"
:error-messages="errors.silent"
@change="change('silent', ...arguments)"
/>
<v-row>
<v-col cols="12">
<smart-switch
label="Don't show in log"
:value="model.silent"
:error-messages="errors.silent"
@change="change('silent', ...arguments)"
/>
</v-col>
</v-row>
</form-section>
<slot />
</form-sections>
@@ -60,9 +126,10 @@
import DAMAGE_TYPES from '/imports/constants/DAMAGE_TYPES.js';
import propertyFormMixin from '/imports/client/ui/properties/forms/shared/propertyFormMixin.js';
import VARIABLE_NAME_REGEX from '/imports/constants/VARIABLE_NAME_REGEX.js';
import saveListMixin from '/imports/client/ui/properties/forms/shared/lists/saveListMixin.js';
export default {
mixins: [propertyFormMixin],
mixins: [propertyFormMixin, saveListMixin],
props: {
parentTarget: {
type: String,
@@ -102,6 +169,13 @@ export default {
return hints[this.model.target];
}
},
methods: {
saveChange({ path, value, ack }) {
console.log({ path, value, ack });
this.$emit('change', {path: [ 'save', ...path ], value, ack})
this.$emit('change', {path: [ 'silent' ], value: true, ack})
},
},
}
</script>

View File

@@ -84,12 +84,30 @@
<form-section
name="Behavior"
>
<smart-switch
label="Show increment button"
:value="model.showIncrement"
:error-messages="errors.showIncrement"
@change="change('showIncrement', ...arguments)"
/>
<v-row dense>
<v-col
cols="12"
md="6"
>
<smart-switch
label="Show increment button"
:value="model.showIncrement"
:error-messages="errors.showIncrement"
@change="change('showIncrement', ...arguments)"
/>
</v-col>
<v-col
cols="12"
md="6"
>
<smart-switch
label="Don't show in log"
:value="model.silent"
:error-messages="errors.silent"
@change="change('silent', ...arguments)"
/>
</v-col>
</v-row>
</form-section>
<form-section
name="Attunement"

View File

@@ -29,7 +29,6 @@
</v-col>
<v-col
cols="12"
md="6"
>
<smart-toggle
label="Target creature"

View File

@@ -1,6 +1,9 @@
<template lang="html">
<div>
<div class="layout align-center justify-start" style="height:40px;">
<div
class="layout align-center justify-start"
style="height:40px;"
>
<v-icon
v-if="!hideIcon"
class="mr-2"
@@ -15,19 +18,24 @@
<span v-if="model.target === 'self'">to self</span>
</div>
</div>
<div
v-if="showExternalDetails"
v-for="effect in model.amount.effects">
<div v-if="effect.amount.value !== 0"
style="position:relative; top:-15px; left:5px; height:25px;">
<inline-effect
hide-breadcrumbs
:key="effect._id"
:data-id="effect._id"
:model="effect"
/>
<template v-if="showExternalDetails">
<div
v-for="effect in (model.amount && model.amount.effects)"
:key="effect._id"
>
<div
v-if="effect.amount.value !== 0"
style="position:relative; top:-15px; left:5px; height:25px;"
>
<inline-effect
:key="effect._id"
hide-breadcrumbs
:data-id="effect._id"
:model="effect"
/>
</div>
</div>
</div>
</template>
</div>
</template>
@@ -37,8 +45,8 @@ import { getPropertyIcon } from '/imports/constants/PROPERTIES.js';
import InlineEffect from '../components/effects/InlineEffect.vue';
export default {
mixins: [treeNodeViewMixin],
components: {InlineEffect},
mixins: [treeNodeViewMixin],
computed: {
icon() {
if (this.model.damageType === 'healing') {

View File

@@ -65,7 +65,7 @@
:value="reset"
/>
<property-field
v-if="model.resources.conditions.length"
v-if="model.resources.conditions && model.resources.conditions.length"
name="Conditions"
>
<div style="width: 100%;">

View File

@@ -16,6 +16,23 @@
name="Target"
value="Self"
/>
<template v-if="model.save">
<property-field
name="DC"
large
center
:calculation="model.save.dc"
/>
<property-field
name="Save"
mono
:value="model.save.stat"
/>
<property-field
name="On a successful saving throw"
v-bind="saveDamage"
/>
</template>
</v-row>
</div>
</template>
@@ -30,6 +47,16 @@ export default {
if (this.model.damageType === 'healing') return this.model.damageType;
return `${this.model.damageType} damage`
},
saveDamage() {
if (!this.model.save) return;
if (!this.model.save.damageFunction?.calculation) {
return { value: 'Half damage' };
}
if (this.model.save.damageFunction.calculation == '0' || this.model.save.damageFunction.value === 0) {
return { value: 'No damage' };
}
return { calculation: this.model.save.damageFunction };
}
}
}
</script>

View File

@@ -0,0 +1,31 @@
const roman = {
: 1000,
: 900,
: 500,
: 400,
: 100,
: 90,
: 50,
XL: 40,
: 12,
: 11,
: 10,
: 9,
: 8,
: 7,
: 6,
: 5,
: 4,
: 3,
: 2,
: 1
};
export default function romanize(num) {
var str = '';
for (var i of Object.keys(roman)) {
var q = Math.floor(num / roman[i]);
num -= q * roman[i];
str += i.repeat(q);
}
return str;
}