Minor redesign of printed character sheets

This commit is contained in:
ThaumRystra
2023-09-22 22:10:11 +02:00
parent df8f9c085f
commit 643e7892c8
7 changed files with 366 additions and 198 deletions

View File

@@ -32,7 +32,10 @@
light light
> >
<div class="page pa-3"> <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="logo-background" />
<div class="creature-name mr-3"> <div class="creature-name mr-3">
{{ creature.name }} {{ creature.name }}
@@ -59,7 +62,7 @@
</div> </div>
<div <div
class="text-right mt-3 mr-4" 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 }} {{ creatureUrl }}
</div> </div>
@@ -234,7 +237,7 @@ export default {
.character-sheet-printed { .character-sheet-printed {
background: white; background: white;
color: black; color: black;
font-size: 11pt; font-size: 10pt;
} }
.character-sheet-printed * { .character-sheet-printed * {
@@ -247,6 +250,14 @@ export default {
padding: 4px; 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 { .character-sheet-printed .column-layout, .character-sheet-printed .column-layout.wide-columns {
position:relative; position:relative;
width: 100%; width: 100%;
@@ -256,6 +267,10 @@ export default {
column-fill: balance-all; column-fill: balance-all;
} }
.character-sheet-printed .column-layout {
column-width: 200px;
}
.character-sheet-printed .column-layout>div { .character-sheet-printed .column-layout>div {
position:relative; position:relative;
} }
@@ -267,9 +282,10 @@ export default {
opacity: 1 !important; opacity: 1 !important;
} }
.character-sheet-printed .creature-name { .character-sheet-printed .creature-name {
font-size: 24pt; font-size: 16pt;
background-color: white; background-color: white;
} }
.character-sheet-printed .logo-background { .character-sheet-printed .logo-background {
width: 60px; width: 60px;
height: 60px; height: 60px;
@@ -284,6 +300,10 @@ export default {
max-width: unset; max-width: unset;
} }
.character-sheet-printed .tree-node-title {
min-height: unset !important;
}
.character-sheet-printed .double-border { .character-sheet-printed .double-border {
position: relative; position: relative;
padding: 11px 10px; padding: 11px 10px;
@@ -291,6 +311,8 @@ export default {
border-image-slice: 110 126 fill; border-image-slice: 110 126 fill;
border-image-width: 16px; border-image-width: 16px;
border-image-repeat: stretch; border-image-repeat: stretch;
box-decoration-break: clone;
page-break-inside: avoid;
} }
.character-sheet-printed .octagon-border { .character-sheet-printed .octagon-border {
@@ -298,6 +320,8 @@ export default {
padding: 4px 20px; padding: 4px 20px;
border-image: url(/images/print/octagonBorder.png) 124 118 fill; border-image: url(/images/print/octagonBorder.png) 124 118 fill;
border-image-width: 22px; border-image-width: 22px;
box-decoration-break: clone;
page-break-inside: avoid;
} }
.character-sheet-printed .span-all { .character-sheet-printed .span-all {
@@ -322,6 +346,7 @@ export default {
.character-sheet-printed .span-all { .character-sheet-printed .span-all {
column-span: all; column-span: all;
display: block;
} }
@media screen { @media screen {
@@ -337,7 +362,7 @@ export default {
@media print { @media print {
@page { @page {
size: auto; size: auto;
margin: 8mm 8mm 8mm 8mm; margin: 8mm;
} }
body { body {
margin: 0; margin: 0;

View File

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

View File

@@ -1,21 +1,29 @@
<template lang="html"> <template lang="html">
<div class="stats"> <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 <div
v-for="ability in abilities" v-for="ability in abilities"
:key="ability._id" :key="ability._id"
> >
<div <div
class="ability" class="ability ma-0"
> >
<div class="score"> <div class="score">
<div class="double-border top big-number"> <div class="double-border top">
<template v-if="creature.settings.swapScoresAndMods"> <div class="label text-center mb-0">
{{ ability.total }} {{ ability.name }}
</template> </div>
<template v-else> <div class="big-number mb-1">
{{ numberToSignedString(ability.modifier) }} <template v-if="creature.settings.swapScoresAndMods">
</template> {{ ability.total }}
</template>
<template v-else>
{{ numberToSignedString(ability.modifier) }}
</template>
</div>
</div> </div>
<div class="bottom"> <div class="bottom">
<template v-if="creature.settings.swapScoresAndMods"> <template v-if="creature.settings.swapScoresAndMods">
@@ -26,12 +34,10 @@
</template> </template>
</div> </div>
</div> </div>
<div class="double-border name label">
{{ ability.name }}
</div>
</div> </div>
</div> </div>
</div>
<column-layout>
<div <div
v-for="toggle in toggles" v-for="toggle in toggles"
:key="toggle._id" :key="toggle._id"
@@ -105,7 +111,7 @@
<div class="label"> <div class="label">
Total: {{ healthBar.total }} Total: {{ healthBar.total }}
</div> </div>
<div style="height: 60px;" /> <div style="height: 40px;" />
<div <div
style="text-align: center;" style="text-align: center;"
class="label" class="label"
@@ -136,7 +142,7 @@
{{ hitDie.total }}{{ hitDie.hitDiceSize }} {{ hitDie.total }}{{ hitDie.hitDiceSize }}
</span> </span>
</div> </div>
<div style="height: 60px;" /> <div style="height: 40px;" />
<div <div
style="text-align: center;" style="text-align: center;"
class="label" class="label"
@@ -232,6 +238,13 @@
:model="save" :model="save"
:data-id="save._id" :data-id="save._id"
/> />
<div
v-for="(effect) in saveConditionals"
:key="effect._id"
class="mt-2"
>
* {{ effect.text }}
</div>
<div class="label text-center"> <div class="label text-center">
Saving Throws Saving Throws
</div> </div>
@@ -250,6 +263,13 @@
:model="skill" :model="skill"
:data-id="skill._id" :data-id="skill._id"
/> />
<div
v-for="(effect) in skillConditionals"
:key="effect._id"
class="mt-2"
>
* {{ effect.text }}
</div>
<div class="label text-center"> <div class="label text-center">
Skills Skills
</div> </div>
@@ -261,69 +281,20 @@
<div <div
class="double-border" class="double-border"
> >
<printed-skill <p>
v-for="weapon in weapons" <b>Weapons:</b> {{ weapons.map(p => p.name).join(', ') }}
:key="weapon._id" </p>
hide-modifier <p>
:model="weapon" <b>Armor:</b> {{ armors.map(p => p.name).join(', ') }}
:data-id="weapon._id" </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"> <div class="label text-center">
Weapons Proficiencies
</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>
</div> </div>
@@ -381,6 +352,7 @@ import numberToSignedString from '../../../../../api/utility/numberToSignedStrin
import PrintedSkill from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedSkill.vue'; 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 PrintedDamageMultipliers from '/imports/client/ui/creature/character/printedCharacterSheet/components/PrintedDamageMultipliers.vue';
import PropertyDescription from '/imports/client/ui/properties/viewers/shared/PropertyDescription.vue'; import PropertyDescription from '/imports/client/ui/properties/viewers/shared/PropertyDescription.vue';
import { uniqBy } from 'lodash';
const getProperties = function (creature, filter, options = { const getProperties = function (creature, filter, options = {
sort: { order: 1 } sort: { order: 1 }
@@ -483,9 +455,31 @@ export default {
savingThrows() { savingThrows() {
return getSkillOfType(this.creature, 'save'); 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() { skills() {
return getSkillOfType(this.creature, 'skill'); 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() { tools() {
return getSkillOfType(this.creature, 'tool'); return getSkillOfType(this.creature, 'tool');
}, },
@@ -557,10 +551,10 @@ export default {
align-items: center; align-items: center;
} }
.ability .top { .ability .top {
min-width: 64px; min-width: 86px;
text-align: center; text-align: center;
margin-bottom: -10px; margin: 4px 4px -10px;
padding: 14px; padding: 8px;
z-index: 1; z-index: 1;
} }
.ability .bottom { .ability .bottom {

View File

@@ -3,36 +3,21 @@
class="action-card" class="action-card"
:class="cardClasses" :class="cardClasses"
> >
<div class="label text-center"> <div class="d-flex align-center mb-2">
{{ actionTypeName }} <div class="roll-bonus">
</div> <template v-if="!onHitDamage && rollBonus">
<div class="d-flex align-center"> {{ rollBonus }}
</template>
</div>
<div class="action-title text-center flex-grow-1">
{{ model.name || propertyName }}
</div>
<div class="avatar"> <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 <property-icon
v-else
:model="model" :model="model"
color="rgba(0,0,0,0.7)" color="rgba(0,0,0,0.7)"
/> />
</div> </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>
<div <div
v-if="Number.isFinite(model.uses)" v-if="Number.isFinite(model.uses)"
@@ -40,7 +25,7 @@
> >
{{ model.uses }} uses {{ model.uses }} uses
</div> </div>
<div class="pb-3"> <div>
<div <div
v-if="model.resources && model.resources.attributesConsumed.length || v-if="model.resources && model.resources.attributesConsumed.length ||
model.resources.itemsConsumed.length" model.resources.itemsConsumed.length"
@@ -65,14 +50,35 @@
<template v-if="model.summary"> <template v-if="model.summary">
<markdown-text :markdown="model.summary.value || model.summary.text" /> <markdown-text :markdown="model.summary.value || model.summary.text" />
</template> </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 <tree-node-list
v-if="children && children.length" v-else-if="children && children.length"
start-expanded start-expanded
show-external-details
:children="children" :children="children"
@selected="e => $emit('sub-click', e)" @selected="e => $emit('sub-click', e)"
/> />
</div> </div>
<div class="action-subtitle text-center">
{{ actionTypeName }}
</div>
</div> </div>
</template> </template>
@@ -87,6 +93,8 @@ import TreeNodeList from '/imports/client/ui/components/tree/TreeNodeList.vue';
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'; import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import { some } from 'lodash'; 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 { export default {
components: { components: {
@@ -148,7 +156,24 @@ export default {
'free': 'Free Action', 'free': 'Free Action',
'long': 'Long Action' 'long': 'Long Action'
}[this.model.actionType] || this.model.actionType }[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: { meteor: {
children() { children() {
@@ -187,28 +212,39 @@ export default {
transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1), transition: box-shadow .4s cubic-bezier(0.25, 0.8, 0.25, 1),
transform 0.075s ease; transform 0.075s ease;
} }
.roll-bonus {
.avatar {
font-size: 18pt; font-size: 18pt;
text-align: center; flex-basis: 24px;
min-width: 40px; }
min-height: 40px; .avatar {
min-width: 24px;
min-height: 24px;
line-height: 24px;
} }
.label { .label {
font-size: 10pt; font-size: 10pt;
font-variant: small-caps; font-variant: all-small-caps;
flex-grow: 1; flex-grow: 1;
} }
.damage {
font-size: 12pt;
font-weight: 500;
}
.action-title { .action-title {
font-size: 16px; font-size: 12pt;
font-weight: 400; font-weight: 600;
line-height: 24px; min-height: 24px;
position: relative; display: flex;
text-align: left; align-items: center;
transition: .3s cubic-bezier(.25, .8, .5, 1); justify-content: center;
width: 100%; font-variant: all-small-caps;
}
.action-subtitle {
font-variant: all-small-caps;
font-size: 11pt;
} }
.resources { .resources {

View File

@@ -0,0 +1,122 @@
<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: 500;
text-align: center;
}
.each {
font-weight: 300;
}
.total {
}
.weight-value {
}
.value {
min-width: 40px;
text-align: end;
}
.weight {
min-width: 40px;
text-align: end;
}
</style>

View File

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