From 0e8d1615a7ff405fc762300c3cfac02e935a702e Mon Sep 17 00:00:00 2001 From: Oliver-Akins Date: Thu, 29 Feb 2024 22:35:28 -0700 Subject: [PATCH] Begin implementation of the Stats / Skills tab --- langs/en-ca.2.json | 42 +++++++++++++ module/config.mjs | 7 ++- module/handlebars.mjs | 5 +- module/helpers/index.mjs | 2 + module/models/Actor/Player.mjs | 25 +++++--- module/sheets/Actors/PC/Improved.mjs | 59 ++++++++++++++++++- module/utils/modifierToString.mjs | 18 ++++++ styles/generic.scss | 1 + styles/mixins/_material.scss | 2 +- .../sheets/actor/char-sheet/pages/stats.scss | 48 +++++++++++++++ .../sheets/actor/char-sheet/themes/dark.scss | 28 +++++---- styles/sheets/actor/char-sheet/v2.scss | 36 ++++++----- styles/sheets/partials/stat.scss | 2 +- system.json | 2 +- .../char-sheet/v2/partials/stats.v2.pc.hbs | 56 ++++++++++++++++++ templates/actors/char-sheet/v2/sheet.hbs | 23 +------- 16 files changed, 292 insertions(+), 64 deletions(-) create mode 100644 langs/en-ca.2.json create mode 100644 module/utils/modifierToString.mjs create mode 100644 styles/sheets/actor/char-sheet/pages/stats.scss create mode 100644 templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json new file mode 100644 index 0000000..6e03f87 --- /dev/null +++ b/langs/en-ca.2.json @@ -0,0 +1,42 @@ +{ + "dotdungeon": { + "stat": { + "build": "Build", + "meta": "Meta", + "presence": "Presence", + "hands": "Hands", + "tilt": "Tilt", + "rng": "RNG" + }, + "skills": { + "defense": "Defense", + "magic": "Magic", + "melee": "Melee", + "platforming": "Platforming", + "strength": "Strength", + "alchemy": "Alchemy", + "arcanum": "Arcanum", + "dreams": "Dreams", + "lore": "Lore", + "navigation": "Navigation", + "animal_handling": "Animal Handling", + "perception": "Perception", + "sneak": "Sneak", + "speech": "Speech", + "vibes": "Vibes", + "accuracy": "Accuracy", + "crafting": "Crafting", + "engineering": "Engineering", + "explosives": "Explosives", + "piloting": "Piloting" + }, + "die": { + "d4": "d4", + "d6": "d6", + "d8": "d8", + "d10": "d10", + "d12": "d12", + "d20": "d20" + } + } +} \ No newline at end of file diff --git a/module/config.mjs b/module/config.mjs index 61caf63..664ecd2 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1,6 +1,11 @@ export const statDice = [ `d4`, `d6`, `d8`, `d10`, `d12`, `d20` ]; -export const trainingLevels = [``, `locked`, `+2`, `+4`]; +export const trainingLevels = { + locked: -1, + untrained: 0, + trained: 2, + expert: 4 +} export const damageTypes = [ `slashing`, `piercing`, `smashing`, `gun`, `neon`, `shadow`, `solar` ]; diff --git a/module/handlebars.mjs b/module/handlebars.mjs index ff1c80d..bd91425 100644 --- a/module/handlebars.mjs +++ b/module/handlebars.mjs @@ -7,7 +7,7 @@ export const partials = [ `partials/panel.hbs`, `items/aspect.hbs`, - // All of the partials for the PC sheet panels + // All of the partials for the PC MVP sheet panels `actors/char-sheet-mvp/panels/aspect.pc.hbs`, `actors/char-sheet-mvp/panels/backpack.pc.hbs`, `actors/char-sheet-mvp/panels/mounts.pc.hbs`, @@ -18,6 +18,9 @@ export const partials = [ `actors/char-sheet-mvp/panels/pets.pc.hbs`, `actors/char-sheet-mvp/panels/sync.pc.hbs`, `actors/char-sheet-mvp/panels/weapons.pc.hbs`, + + // The v2 PC sheet partials + `actors/char-sheet/v2/partials/stats.v2.pc.hbs`, ]; export const icons = [ diff --git a/module/helpers/index.mjs b/module/helpers/index.mjs index 246c04d..a8456b3 100644 --- a/module/helpers/index.mjs +++ b/module/helpers/index.mjs @@ -3,6 +3,7 @@ import { createArray } from "./createArray.mjs"; import { detailsExpanded } from "./detailsExpanded.mjs"; import { objectValue } from "./objectValue.mjs"; import { toFriendlyDuration } from "./toFriendlyDuration.mjs"; +import { localizer } from "../utils/localizer.mjs"; export default { @@ -12,6 +13,7 @@ export default { "dd-toFriendlyDuration": toFriendlyDuration, "dd-objectValue": objectValue, "dd-expanded": detailsExpanded, + "dd-i18n": localizer, // Simple helpers "dd-stringify": v => JSON.stringify(v, null, ` `), diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index bd554ad..3d75709 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -1,4 +1,5 @@ import { MappingField } from "../fields/MappingField.mjs"; +import DOTDUNGEON from "../../config.mjs"; function diceChoiceField() { return new foundry.data.fields.StringField({ @@ -6,17 +7,17 @@ function diceChoiceField() { blank: true, trim: true, options() { - return CONFIG.DOTDUNGEON.statDice; + return DOTDUNGEON.statDice; }, }); }; function trainingLevelField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - trim: true, - options: CONFIG.DOTDUNGEON.trainingLevels, + return new foundry.data.fields.NumberField({ + initial: 0, + min: -1, + integer: true, + options: Object.values(DOTDUNGEON.trainingLevels), }); }; @@ -24,7 +25,7 @@ function weaponDamageTypeField() { return new foundry.data.fields.StringField({ initial: ``, blank: true, - options: [ ``, ...CONFIG.DOTDUNGEON.damageTypes ], + options: [ ``, ...DOTDUNGEON.damageTypes ], }); }; @@ -32,7 +33,7 @@ function ammoTypeField() { return new foundry.data.fields.StringField({ initial: ``, blank: true, - options: [ ``, ...CONFIG.DOTDUNGEON.ammoTypes ], + options: [ ``, ...DOTDUNGEON.ammoTypes ], }); }; @@ -40,6 +41,14 @@ export class PlayerData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; return { + /* + These are special data properties that will be used by ActiveEffects + to modify certain limits within the actor, allowing for neat hacks + that change these + */ + weapon_slots: new fields.NumberField({ initial: 2 }), + inventory_slots: new fields.NumberField({ initial: 0 }), + bytes: new fields.NumberField({ initial: 0, min: 0, diff --git a/module/sheets/Actors/PC/Improved.mjs b/module/sheets/Actors/PC/Improved.mjs index 59f7635..a84b852 100644 --- a/module/sheets/Actors/PC/Improved.mjs +++ b/module/sheets/Actors/PC/Improved.mjs @@ -1,4 +1,7 @@ import { GenericActorSheet } from "../../GenericActorSheet.mjs"; +import DOTDUNGEON from "../../../config.mjs"; +import { localizer } from "../../../utils/localizer.mjs"; +import { modifierToString } from "../../../utils/modifierToString.mjs"; export class PlayerSheetv2 extends GenericActorSheet { static get defaultOptions() { @@ -10,8 +13,8 @@ export class PlayerSheetv2 extends GenericActorSheet { { group: `page`, navSelector: `nav`, - contentSelector: `.tab-content`, - initial: `tab1`, + contentSelector: `.page-content`, + initial: `stats`, }, ], } @@ -40,8 +43,58 @@ export class PlayerSheetv2 extends GenericActorSheet { ctx.computed = { canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM, canAddAspect: !await actor.proxyFunction.bind(actor)(`atAspectLimit`), + stats: this.#statData, }; - + console.log(ctx) return ctx; }; + + get #statData() { + const stats = []; + const usedDice = new Set(Object.values(this.actor.system.stats)); + for (const statName in this.actor.system.stats) { + const stat = { + key: statName, + name: localizer(`dotdungeon.stat.${statName}`), + value: this.actor.system.stats[statName], + }; + + /* + Determine what dice are available to the user in the dropdown + selector. Disables all dice options that are selected, but not used + by this stat. + */ + stat.dieOptions = DOTDUNGEON.statDice.map(die => { + return { + value: die, + label: localizer(`dotdungeon.die.${die}`, { stat: statName }), + disabled: usedDice.has(die) && this.actor.system.stats[statName] !== die, + }; + }); + + /* + Calculating the data needed in order to display all of the skills + for this character. + */ + stat.skills = []; + for (const skill in this.actor.system.skills[statName]) { + const value = this.actor.system.skills[statName][skill]; + stat.skills.push({ + key: skill, + name: localizer(`dotdungeon.skills.${skill}`), + value, + formula: `1` + stat.value + modifierToString(value), + rollDisabled: stat.value === `` || value === `locked`, + }); + }; + + stats.push(stat); + }; + return stats; + }; + + _updateObject(...args) { + console.log(args) + super._updateObject(...args); + }; } \ No newline at end of file diff --git a/module/utils/modifierToString.mjs b/module/utils/modifierToString.mjs new file mode 100644 index 0000000..2d1c59c --- /dev/null +++ b/module/utils/modifierToString.mjs @@ -0,0 +1,18 @@ +/** + * Takes in an integer and converts it into a string format that can be used in + * roll formulas or for displaying to the user. + * + * @param {number} mod The modifier to stringify + * @param {object} opts + * @param {boolean} opts.spaces Puts spaces on either side of the operand + * @returns {string} + */ +export function modifierToString(mod, opts = {}) { + if (mod == 0) return ``; + + let value = [``, `+`, mod] + if (mod < 0) { + value = [``, `-`, Math.abs(mod)] + }; + return value.join(opts.spaces ? ` ` : ``); +}; diff --git a/styles/generic.scss b/styles/generic.scss index 6769ebc..ae3bce9 100644 --- a/styles/generic.scss +++ b/styles/generic.scss @@ -16,6 +16,7 @@ h2, h3, h4, h5, h6 { @include fvtt_reset; + color: inherit; font-family: $title-font; margin: 0; } diff --git a/styles/mixins/_material.scss b/styles/mixins/_material.scss index bd4d68d..2eb9bec 100644 --- a/styles/mixins/_material.scss +++ b/styles/mixins/_material.scss @@ -1,5 +1,5 @@ @mixin elevate($height) { - background-color: var(--elevation-#{$height}dp); + background-color: var(--elevation-#{$height}dp-bg); -webkit-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); -moz-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); diff --git a/styles/sheets/actor/char-sheet/pages/stats.scss b/styles/sheets/actor/char-sheet/pages/stats.scss new file mode 100644 index 0000000..995336a --- /dev/null +++ b/styles/sheets/actor/char-sheet/pages/stats.scss @@ -0,0 +1,48 @@ +.dotdungeon .actor--pc .active.stats-panel { + display: grid; + height: 100%; + gap: 16px; + grid-template-columns: 1fr 1fr; + grid-template-rows: auto auto auto; + + .stat { + border-radius: 8px; + color: white; + + select { + height: 100%; + outline: none; + border: none; + } + + &__header { + padding: 8px; + display: flex; + align-items: center; + flex-direction: row; + color: var(--stat-divider-text-color); + gap: 8px; + > :first-child { + flex-grow: 1; + } + &:not(:only-child) { + border-bottom: 1px solid var(--stat-divider-color); + } + } + + &__skills { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; + margin: 8px; + align-items: center; + label { + text-align: end; + justify-self: right; + } + button { + margin-right: 25%; + } + } + } +} \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/themes/dark.scss b/styles/sheets/actor/char-sheet/themes/dark.scss index 0e8bde1..e315e3a 100644 --- a/styles/sheets/actor/char-sheet/themes/dark.scss +++ b/styles/sheets/actor/char-sheet/themes/dark.scss @@ -2,8 +2,8 @@ $t: transparent; $background: #0a0a0a; $surface: #121212; -$primary: $t; -$secondary: $t; +$primary: #005300; +$secondary: #6c056c; $on-background: $t; $on-surface: $t; $on-primary: $t; @@ -12,17 +12,19 @@ $on-secondary: $t; .actor--pc { --sheet-bg: #{$background}; --nav-bg: #{$surface}; - --panel-bg: #{$surface}; /* Elevation backgrounds to following Material design */ - --elevation-0dp: #{$surface}; - --elevation-1dp: color-mix(in lab, #{$surface}, white 5%); - --elevation-2dp: color-mix(in lab, #{$surface}, white 7%); - --elevation-3dp: color-mix(in lab, #{$surface}, white 8%); - --elevation-4dp: color-mix(in lab, #{$surface}, white 9%); - --elevation-6dp: color-mix(in lab, #{$surface}, white 11%); - --elevation-8dp: color-mix(in lab, #{$surface}, white 12%); - --elevation-12dp: color-mix(in lab, #{$surface}, white 14%); - --elevation-16dp: color-mix(in lab, #{$surface}, white 15%); - --elevation-24dp: color-mix(in lab, #{$surface}, white 16%); + --elevation-0dp-bg: #{$surface}; + --elevation-1dp-bg: color-mix(in lab, transparent, white 5%); + --elevation-2dp-bg: color-mix(in lab, transparent, white 7%); + --elevation-3dp-bg: color-mix(in lab, transparent, white 8%); + --elevation-4dp-bg: color-mix(in lab, transparent, white 9%); + --elevation-6dp-bg: color-mix(in lab, transparent, white 11%); + --elevation-8dp-bg: color-mix(in lab, transparent, white 12%); + --elevation-12dp-bg: color-mix(in lab, transparent, white 14%); + --elevation-16dp-bg: color-mix(in lab, transparent, white 15%); + --elevation-24dp-bg: color-mix(in lab, transparent, white 16%); + + --stat-divider-color: #{$secondary}; + --stat-header-text-color: white; } diff --git a/styles/sheets/actor/char-sheet/v2.scss b/styles/sheets/actor/char-sheet/v2.scss index d1ef5e3..b6a087e 100644 --- a/styles/sheets/actor/char-sheet/v2.scss +++ b/styles/sheets/actor/char-sheet/v2.scss @@ -1,26 +1,34 @@ @use "./themes/dark.scss"; @use "../../../mixins/material" as material; +@use "./pages/stats.scss"; + .dotdungeon .actor--pc { background-color: var(--sheet-bg); - display: grid; - grid-template-rows: 1fr minmax(min-content, 50px); + position: relative; color: white; - .panel-0dp { @include material.elevate(0); margin: 1rem; padding: 10px; } - .panel-1dp { @include material.elevate(1); margin: 1rem; padding: 10px; } - .panel-2dp { @include material.elevate(2); margin: 1rem; padding: 10px; } - .panel-3dp { @include material.elevate(3); margin: 1rem; padding: 10px; } - .panel-4dp { @include material.elevate(4); margin: 1rem; padding: 10px; } - .panel-6dp { @include material.elevate(6); margin: 1rem; padding: 10px; } - .panel-8dp { @include material.elevate(8); margin: 1rem; padding: 10px; } - .panel-12dp { @include material.elevate(12); margin: 1rem; padding: 10px; } - .panel-16dp { @include material.elevate(16); margin: 1rem; padding: 10px; } - .panel-24dp { @include material.elevate(24); margin: 1rem; padding: 10px; } + .e-0dp { @include material.elevate(0); } + .e-1dp { @include material.elevate(1); } + .e-2dp { @include material.elevate(2); } + .e-3dp { @include material.elevate(3); } + .e-4dp { @include material.elevate(4); } + .e-6dp { @include material.elevate(6); } + .e-8dp { @include material.elevate(8); } + .e-12dp { @include material.elevate(12); } + .e-16dp { @include material.elevate(16); } + .e-24dp { @include material.elevate(24); } nav { background-color: var(--nav-bg); - @include material.elevate(02) + @include material.elevate(02); + position: absolute; + bottom: 0; + width: 100%; } -} + + .page-content { + padding: 16px; + } +} \ No newline at end of file diff --git a/styles/sheets/partials/stat.scss b/styles/sheets/partials/stat.scss index 1875cf3..1937854 100644 --- a/styles/sheets/partials/stat.scss +++ b/styles/sheets/partials/stat.scss @@ -1,4 +1,4 @@ -.dotdungeon .stat { +.dotdungeon .actor--pc-mvp .stat { display: flex; flex-direction: column; align-items: center; diff --git a/system.json b/system.json index 5870577..3c80e4b 100644 --- a/system.json +++ b/system.json @@ -27,7 +27,7 @@ { "lang": "en", "name": "English (Canadian)", - "path": "langs/en-ca.json" + "path": "langs/en-ca.2.json" } ], "packs": [ diff --git a/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs new file mode 100644 index 0000000..f0d2c43 --- /dev/null +++ b/templates/actors/char-sheet/v2/partials/stats.v2.pc.hbs @@ -0,0 +1,56 @@ +
+ {{!-- + Iterate over each stat in the display data + - header: + - localized stat name + - stat dice dropdown + - roll button + - body (if skills present): + - iterate over all of the skills + - localized skill name + - training dropdown + - roll button + --}} + {{#each computed.stats as | stat |}} +
+
+

{{stat.name}}

+ + +
+ {{#if stat.skills}} +
+ {{#each stat.skills as | skill |}} + + + + {{/each}} +
+ {{/if}} +
+ {{/each}} +
\ No newline at end of file diff --git a/templates/actors/char-sheet/v2/sheet.hbs b/templates/actors/char-sheet/v2/sheet.hbs index 7880fa7..0b2e082 100644 --- a/templates/actors/char-sheet/v2/sheet.hbs +++ b/templates/actors/char-sheet/v2/sheet.hbs @@ -1,27 +1,8 @@
{{!-- All panels here --}} -
+
Avatar
-
-
-

Build

-
-
-

Meta

-
-
-

Presence

-
-
-

Hands

-
-
-

Tilt

-
-
-

RNG

-
-
+ {{> dotdungeon.pc.v2.stats }}
Combat
Inventory
Spells