Begin implementation of the Stats / Skills tab

This commit is contained in:
Oliver-Akins 2024-02-29 22:35:28 -07:00
parent 753d72b4e0
commit 0e8d1615a7
16 changed files with 292 additions and 64 deletions

42
langs/en-ca.2.json Normal file
View file

@ -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"
}
}
}

View file

@ -1,6 +1,11 @@
export const statDice = [ `d4`, `d6`, `d8`, `d10`, `d12`, `d20` ]; 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` ]; export const damageTypes = [ `slashing`, `piercing`, `smashing`, `gun`, `neon`, `shadow`, `solar` ];

View file

@ -7,7 +7,7 @@ export const partials = [
`partials/panel.hbs`, `partials/panel.hbs`,
`items/aspect.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/aspect.pc.hbs`,
`actors/char-sheet-mvp/panels/backpack.pc.hbs`, `actors/char-sheet-mvp/panels/backpack.pc.hbs`,
`actors/char-sheet-mvp/panels/mounts.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/pets.pc.hbs`,
`actors/char-sheet-mvp/panels/sync.pc.hbs`, `actors/char-sheet-mvp/panels/sync.pc.hbs`,
`actors/char-sheet-mvp/panels/weapons.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 = [ export const icons = [

View file

@ -3,6 +3,7 @@ import { createArray } from "./createArray.mjs";
import { detailsExpanded } from "./detailsExpanded.mjs"; import { detailsExpanded } from "./detailsExpanded.mjs";
import { objectValue } from "./objectValue.mjs"; import { objectValue } from "./objectValue.mjs";
import { toFriendlyDuration } from "./toFriendlyDuration.mjs"; import { toFriendlyDuration } from "./toFriendlyDuration.mjs";
import { localizer } from "../utils/localizer.mjs";
export default { export default {
@ -12,6 +13,7 @@ export default {
"dd-toFriendlyDuration": toFriendlyDuration, "dd-toFriendlyDuration": toFriendlyDuration,
"dd-objectValue": objectValue, "dd-objectValue": objectValue,
"dd-expanded": detailsExpanded, "dd-expanded": detailsExpanded,
"dd-i18n": localizer,
// Simple helpers // Simple helpers
"dd-stringify": v => JSON.stringify(v, null, ` `), "dd-stringify": v => JSON.stringify(v, null, ` `),

View file

@ -1,4 +1,5 @@
import { MappingField } from "../fields/MappingField.mjs"; import { MappingField } from "../fields/MappingField.mjs";
import DOTDUNGEON from "../../config.mjs";
function diceChoiceField() { function diceChoiceField() {
return new foundry.data.fields.StringField({ return new foundry.data.fields.StringField({
@ -6,17 +7,17 @@ function diceChoiceField() {
blank: true, blank: true,
trim: true, trim: true,
options() { options() {
return CONFIG.DOTDUNGEON.statDice; return DOTDUNGEON.statDice;
}, },
}); });
}; };
function trainingLevelField() { function trainingLevelField() {
return new foundry.data.fields.StringField({ return new foundry.data.fields.NumberField({
initial: ``, initial: 0,
blank: true, min: -1,
trim: true, integer: true,
options: CONFIG.DOTDUNGEON.trainingLevels, options: Object.values(DOTDUNGEON.trainingLevels),
}); });
}; };
@ -24,7 +25,7 @@ function weaponDamageTypeField() {
return new foundry.data.fields.StringField({ return new foundry.data.fields.StringField({
initial: ``, initial: ``,
blank: true, blank: true,
options: [ ``, ...CONFIG.DOTDUNGEON.damageTypes ], options: [ ``, ...DOTDUNGEON.damageTypes ],
}); });
}; };
@ -32,7 +33,7 @@ function ammoTypeField() {
return new foundry.data.fields.StringField({ return new foundry.data.fields.StringField({
initial: ``, initial: ``,
blank: true, blank: true,
options: [ ``, ...CONFIG.DOTDUNGEON.ammoTypes ], options: [ ``, ...DOTDUNGEON.ammoTypes ],
}); });
}; };
@ -40,6 +41,14 @@ export class PlayerData extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { 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({ bytes: new fields.NumberField({
initial: 0, initial: 0,
min: 0, min: 0,

View file

@ -1,4 +1,7 @@
import { GenericActorSheet } from "../../GenericActorSheet.mjs"; 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 { export class PlayerSheetv2 extends GenericActorSheet {
static get defaultOptions() { static get defaultOptions() {
@ -10,8 +13,8 @@ export class PlayerSheetv2 extends GenericActorSheet {
{ {
group: `page`, group: `page`,
navSelector: `nav`, navSelector: `nav`,
contentSelector: `.tab-content`, contentSelector: `.page-content`,
initial: `tab1`, initial: `stats`,
}, },
], ],
} }
@ -40,8 +43,58 @@ export class PlayerSheetv2 extends GenericActorSheet {
ctx.computed = { ctx.computed = {
canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM, canChangeGroup: ctx.settings.playersCanChangeGroup || ctx.isGM,
canAddAspect: !await actor.proxyFunction.bind(actor)(`atAspectLimit`), canAddAspect: !await actor.proxyFunction.bind(actor)(`atAspectLimit`),
stats: this.#statData,
}; };
console.log(ctx)
return 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);
};
} }

View file

@ -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 ? ` ` : ``);
};

View file

@ -16,6 +16,7 @@
h2, h3, h4, h5, h6 { h2, h3, h4, h5, h6 {
@include fvtt_reset; @include fvtt_reset;
color: inherit;
font-family: $title-font; font-family: $title-font;
margin: 0; margin: 0;
} }

View file

@ -1,5 +1,5 @@
@mixin elevate($height) { @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); -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); -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); box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75);

View file

@ -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%;
}
}
}
}

View file

@ -2,8 +2,8 @@ $t: transparent;
$background: #0a0a0a; $background: #0a0a0a;
$surface: #121212; $surface: #121212;
$primary: $t; $primary: #005300;
$secondary: $t; $secondary: #6c056c;
$on-background: $t; $on-background: $t;
$on-surface: $t; $on-surface: $t;
$on-primary: $t; $on-primary: $t;
@ -12,17 +12,19 @@ $on-secondary: $t;
.actor--pc { .actor--pc {
--sheet-bg: #{$background}; --sheet-bg: #{$background};
--nav-bg: #{$surface}; --nav-bg: #{$surface};
--panel-bg: #{$surface};
/* Elevation backgrounds to following Material design */ /* Elevation backgrounds to following Material design */
--elevation-0dp: #{$surface}; --elevation-0dp-bg: #{$surface};
--elevation-1dp: color-mix(in lab, #{$surface}, white 5%); --elevation-1dp-bg: color-mix(in lab, transparent, white 5%);
--elevation-2dp: color-mix(in lab, #{$surface}, white 7%); --elevation-2dp-bg: color-mix(in lab, transparent, white 7%);
--elevation-3dp: color-mix(in lab, #{$surface}, white 8%); --elevation-3dp-bg: color-mix(in lab, transparent, white 8%);
--elevation-4dp: color-mix(in lab, #{$surface}, white 9%); --elevation-4dp-bg: color-mix(in lab, transparent, white 9%);
--elevation-6dp: color-mix(in lab, #{$surface}, white 11%); --elevation-6dp-bg: color-mix(in lab, transparent, white 11%);
--elevation-8dp: color-mix(in lab, #{$surface}, white 12%); --elevation-8dp-bg: color-mix(in lab, transparent, white 12%);
--elevation-12dp: color-mix(in lab, #{$surface}, white 14%); --elevation-12dp-bg: color-mix(in lab, transparent, white 14%);
--elevation-16dp: color-mix(in lab, #{$surface}, white 15%); --elevation-16dp-bg: color-mix(in lab, transparent, white 15%);
--elevation-24dp: color-mix(in lab, #{$surface}, white 16%); --elevation-24dp-bg: color-mix(in lab, transparent, white 16%);
--stat-divider-color: #{$secondary};
--stat-header-text-color: white;
} }

View file

@ -1,26 +1,34 @@
@use "./themes/dark.scss"; @use "./themes/dark.scss";
@use "../../../mixins/material" as material; @use "../../../mixins/material" as material;
@use "./pages/stats.scss";
.dotdungeon .actor--pc { .dotdungeon .actor--pc {
background-color: var(--sheet-bg); background-color: var(--sheet-bg);
display: grid; position: relative;
grid-template-rows: 1fr minmax(min-content, 50px);
color: white; color: white;
.panel-0dp { @include material.elevate(0); margin: 1rem; padding: 10px; } .e-0dp { @include material.elevate(0); }
.panel-1dp { @include material.elevate(1); margin: 1rem; padding: 10px; } .e-1dp { @include material.elevate(1); }
.panel-2dp { @include material.elevate(2); margin: 1rem; padding: 10px; } .e-2dp { @include material.elevate(2); }
.panel-3dp { @include material.elevate(3); margin: 1rem; padding: 10px; } .e-3dp { @include material.elevate(3); }
.panel-4dp { @include material.elevate(4); margin: 1rem; padding: 10px; } .e-4dp { @include material.elevate(4); }
.panel-6dp { @include material.elevate(6); margin: 1rem; padding: 10px; } .e-6dp { @include material.elevate(6); }
.panel-8dp { @include material.elevate(8); margin: 1rem; padding: 10px; } .e-8dp { @include material.elevate(8); }
.panel-12dp { @include material.elevate(12); margin: 1rem; padding: 10px; } .e-12dp { @include material.elevate(12); }
.panel-16dp { @include material.elevate(16); margin: 1rem; padding: 10px; } .e-16dp { @include material.elevate(16); }
.panel-24dp { @include material.elevate(24); margin: 1rem; padding: 10px; } .e-24dp { @include material.elevate(24); }
nav { nav {
background-color: var(--nav-bg); background-color: var(--nav-bg);
@include material.elevate(02) @include material.elevate(02);
position: absolute;
bottom: 0;
width: 100%;
} }
}
.page-content {
padding: 16px;
}
}

View file

@ -1,4 +1,4 @@
.dotdungeon .stat { .dotdungeon .actor--pc-mvp .stat {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View file

@ -27,7 +27,7 @@
{ {
"lang": "en", "lang": "en",
"name": "English (Canadian)", "name": "English (Canadian)",
"path": "langs/en-ca.json" "path": "langs/en-ca.2.json"
} }
], ],
"packs": [ "packs": [

View file

@ -0,0 +1,56 @@
<div class="tab stats-panel" data-group="page" data-tab="stats">
{{!--
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 |}}
<div class="e-1dp stat">
<div class="stat__header">
<h2>{{stat.name}}</h2>
<select
name="system.stats.{{stat.key}}"
class="e-2dp"
>
{{#select stat.value}}
<option value="">---</option>
{{#each stat.dieOptions as | die |}}
<option value="{{die.value}}" {{disabled die.disabled}}>
{{die.label}}
</option>
{{/each}}
{{/select}}
</select>
<button type="button" class="e-2dp">
Roll
</button>
</div>
{{#if stat.skills}}
<div class="stat__skills">
{{#each stat.skills as | skill |}}
<label for="">{{skill.name}}</label>
<select name="" id="" class="e-2dp"></select>
<button
type="button"
class="e-2dp"
{{disabled skill.rollDisabled}}
>
{{#if skill.rollDisabled}}
Locked
{{else}}
{{skill.formula}}
{{/if}}
</button>
{{/each}}
</div>
{{/if}}
</div>
{{/each}}
</div>

View file

@ -1,27 +1,8 @@
<form autocomplete="off" class="actor--pc"> <form autocomplete="off" class="actor--pc">
{{!-- All panels here --}} {{!-- All panels here --}}
<section class="tab-content"> <section class="page-content">
<div class="tab" data-group="page" data-tab="avatar">Avatar</div> <div class="tab" data-group="page" data-tab="avatar">Avatar</div>
<div class="tab" data-group="page" data-tab="stats"> {{> dotdungeon.pc.v2.stats }}
<div class="panel-1dp">
<h2>Build</h2>
</div>
<div class="panel-1dp">
<h2>Meta</h2>
</div>
<div class="panel-1dp">
<h2>Presence</h2>
</div>
<div class="panel-1dp">
<h2>Hands</h2>
</div>
<div class="panel-1dp">
<h2>Tilt</h2>
</div>
<div class="panel-1dp">
<h2>RNG</h2>
</div>
</div>
<div class="tab" data-group="page" data-tab="combat">Combat</div> <div class="tab" data-group="page" data-tab="combat">Combat</div>
<div class="tab" data-group="page" data-tab="inventory">Inventory</div> <div class="tab" data-group="page" data-tab="inventory">Inventory</div>
<div class="tab" data-group="page" data-tab="spells">Spells</div> <div class="tab" data-group="page" data-tab="spells">Spells</div>